Rollback of r10216 "Re-implement runAsync to improve code size", caused some subtle errors.
Review by: zundel@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10221 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java
index 3432dd3..d44153b 100644
--- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java
@@ -19,7 +19,6 @@
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JRunAsync;
import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
import com.google.gwt.util.tools.Utility;
@@ -113,14 +112,10 @@
try {
printPre();
- for (JMethod method : jprogram.getEntryMethods()) {
+ for (JMethod method : jprogram.getAllEntryMethods()) {
dependencyAnalyzer.traverseFrom(method);
maybeFlushOutput();
}
- for (JRunAsync runAsync : jprogram.getRunAsyncs()) {
- dependencyAnalyzer.traverseFromRunAsync(runAsync);
- maybeFlushOutput();
- }
printPost();
flushOutput();
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java
index bb507f8..8630ef4 100644
--- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java
@@ -16,15 +16,17 @@
package com.google.gwt.core.ext.soyc.impl;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JRunAsync;
+import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement;
import com.google.gwt.dev.util.HtmlTextOutput;
+import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.util.tools.Utility;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
-import java.util.List;
+import java.util.Map;
import java.util.zip.GZIPOutputStream;
/**
@@ -53,21 +55,21 @@
htmlOut.indentIn();
htmlOut.indentIn();
- List<JRunAsync> runAsyncs = jprogram.getRunAsyncs();
- if (runAsyncs.size() > 0) {
+ Map<Integer, String> splitPointMap = splitPointNames(jprogram);
+ if (splitPointMap.size() > 0) {
curLine = "<splitpoints>";
htmlOut.printRaw(curLine);
htmlOut.newline();
htmlOut.indentIn();
htmlOut.indentIn();
- for (JRunAsync runAsync : runAsyncs) {
- int sp = runAsync.getSplitPoint();
- String name = runAsync.getName();
- curLine = "<splitpoint id=\"" + sp + "\" location=\"" + name + "\"/>";
+ for (int sp = 1; sp <= splitPointMap.size(); sp++) {
+ String location = splitPointMap.get(sp);
+ assert location != null;
+ curLine = "<splitpoint id=\"" + sp + "\" location=\"" + location + "\"/>";
htmlOut.printRaw(curLine);
htmlOut.newline();
if (logger.isLoggable(TreeLogger.TRACE)) {
- logger.log(TreeLogger.TRACE, "Assigning split point #" + sp + " for '" + name + "'");
+ logger.log(TreeLogger.TRACE, "Assigning split point #" + sp + " in method " + location);
}
}
htmlOut.indentOut();
@@ -111,6 +113,37 @@
}
}
+ private static String fullMethodDescription(JMethod method) {
+ return (method.getEnclosingType().getName() + "." + JProgram.getJsniSig(method));
+ }
+
+ /**
+ * Choose human-readable names for the split points.
+ */
+ private static Map<Integer, String> splitPointNames(JProgram program) {
+ Map<Integer, String> names = new HashMap<Integer, String>();
+ Map<String, Integer> counts = new HashMap<String, Integer>();
+ for (RunAsyncReplacement replacement : program.getRunAsyncReplacements().values()) {
+ int entryNumber = replacement.getNumber();
+ String methodDescription;
+ if (replacement.getName() != null) {
+ methodDescription = replacement.getName();
+ } else {
+ methodDescription = "@" + fullMethodDescription(replacement.getEnclosingMethod());
+ if (counts.containsKey(methodDescription)) {
+ counts.put(methodDescription, counts.get(methodDescription) + 1);
+ methodDescription += "#" + Integer.toString(counts.get(methodDescription));
+ } else {
+ counts.put(methodDescription, 1);
+ }
+ }
+
+ names.put(entryNumber, methodDescription);
+ }
+
+ return names;
+ }
+
private SplitPointRecorder() {
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
index fe180a1..6f03ac9 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
@@ -25,8 +25,10 @@
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -52,6 +54,7 @@
}
}
+ public static final String ASYNC_MAGIC_METHOD = "runAsync";
public static final String MAGIC_CLASS = "com.google.gwt.core.client.GWT";
public static final String REBIND_MAGIC_METHOD = "create";
@@ -65,6 +68,8 @@
private final Map<String, MessageSendSite> results = new HashMap<String, MessageSendSite>();
+ private final List<MessageSendSite> runAsyncCalls = new ArrayList<MessageSendSite>();
+
@Override
public void endVisit(MessageSend messageSend, BlockScope scope) {
if (messageSend.binding == null) {
@@ -73,8 +78,10 @@
}
String methodName = String.valueOf(messageSend.selector);
- if (!methodName.equals(REBIND_MAGIC_METHOD)) {
- // Not the create() method.
+ boolean rebindMagicMethod = methodName.equals(REBIND_MAGIC_METHOD);
+ boolean asyncMagicMethod = methodName.equals(ASYNC_MAGIC_METHOD);
+ if (!(rebindMagicMethod || asyncMagicMethod)) {
+ // Not the create() method or the runAsync() method.
return;
}
@@ -88,13 +95,33 @@
MessageSendSite site = new MessageSendSite(messageSend, scope);
Expression[] args = messageSend.arguments;
- if (args.length != 1) {
- reportRebindProblem(site, "GWT.create() should take exactly one argument");
- return;
+ if (rebindMagicMethod) {
+ if (args.length != 1) {
+ reportRebindProblem(site, "GWT.create() should take exactly one argument");
+ return;
+ }
+
+ if (!(args[0] instanceof ClassLiteralAccess)) {
+ reportRebindProblem(site, "Only class literals may be used as arguments to GWT.create()");
+ return;
+ }
+ } else {
+ assert asyncMagicMethod;
+ if (args.length != 1 && args.length != 2) {
+ reportRebindProblem(site, "GWT.runAsync() should take one or two arguments");
+ return;
+ }
+ if (args.length == 2) {
+ if (!(args[0] instanceof ClassLiteralAccess)) {
+ reportRebindProblem(site,
+ "Only class literals may be used to name a call to GWT.runAsync()");
+ return;
+ }
+ }
}
- if (!(args[0] instanceof ClassLiteralAccess)) {
- reportRebindProblem(site, "Only class literals may be used as arguments to GWT.create()");
+ if (asyncMagicMethod) {
+ runAsyncCalls.add(new MessageSendSite(messageSend, scope));
return;
}
@@ -106,6 +133,13 @@
}
}
+ /**
+ * Return the calls to GWT.runAsync() that were seen.
+ */
+ public List<MessageSendSite> getRunAsyncSites() {
+ return runAsyncCalls;
+ }
+
public Map<String, MessageSendSite> getSites() {
return Collections.unmodifiableMap(results);
}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java b/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
index 3492108..aa43758 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
@@ -19,6 +19,7 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.javac.ArtificialRescueChecker;
import com.google.gwt.dev.jdt.FindDeferredBindingSitesVisitor.MessageSendSite;
+import com.google.gwt.dev.jjs.impl.FragmentLoaderCreator;
import com.google.gwt.dev.jjs.impl.TypeLinker;
import com.google.gwt.dev.util.Empty;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
@@ -56,14 +57,19 @@
return results;
}
+ private final FragmentLoaderCreator fragmentLoaderCreator;
private final RebindPermutationOracle rebindPermOracle;
/**
- * Construct a WebModeCompilerFrontEnd.
+ * Construct a WebModeCompilerFrontEnd. The reason a
+ * {@link FragmentLoaderCreator} needs to be passed in is that it uses
+ * generator infrastructure, and therefore needs access to more parts of the
+ * compiler than WebModeCompilerFrontEnd currently has.
*/
private WebModeCompilerFrontEnd(RebindPermutationOracle rebindPermOracle, TypeLinker linker) {
super(rebindPermOracle.getCompilationState(), linker);
this.rebindPermOracle = rebindPermOracle;
+ this.fragmentLoaderCreator = new FragmentLoaderCreator(rebindPermOracle.getGeneratorContext());
}
@Override
@@ -104,6 +110,26 @@
}
}
+ /*
+ * Create a a fragment loader for each GWT.runAsync call. They must be
+ * created now, rather than in ReplaceRunAsyncs, because all generated
+ * classes need to be created before GenerateJavaAST. Note that the loaders
+ * created are not yet associated with the specific sites. The present task
+ * is only to make sure that enough loaders exist. The real association
+ * between loaders and runAsync sites will be made in ReplaceRunAsyncs.
+ */
+ for (MessageSendSite site : v.getRunAsyncSites()) {
+ String resultType;
+ try {
+ resultType = fragmentLoaderCreator.create(logger);
+ dependentTypeNames.add(resultType);
+ doFinish = true;
+ } catch (UnableToCompleteException e) {
+ FindDeferredBindingSitesVisitor.reportRebindProblem(site,
+ "Failed to create a runAsync fragment loader");
+ }
+ }
+
if (doFinish) {
rebindPermOracle.getGeneratorContext().finish(logger);
}
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 4336215..0845492 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -80,6 +80,7 @@
import com.google.gwt.dev.jjs.impl.EqualityNormalizer;
import com.google.gwt.dev.jjs.impl.Finalizer;
import com.google.gwt.dev.jjs.impl.FixAssignmentToUnbox;
+import com.google.gwt.dev.jjs.impl.FragmentLoaderCreator;
import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
@@ -515,6 +516,7 @@
Collections.addAll(allRootTypes, additionalRootTypes);
allRootTypes.addAll(JProgram.CODEGEN_TYPES_SET);
allRootTypes.addAll(JProgram.INDEX_TYPES_SET);
+ allRootTypes.add(FragmentLoaderCreator.ASYNC_FRAGMENT_LOADER);
/*
* Add all SingleJsoImpl types that we know about. It's likely that the
* concrete types are never explicitly referenced.
@@ -1293,7 +1295,12 @@
ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(program);
cfa.setDependencyRecorder(deps);
- cfa.traverseEntryMethods();
+ for (List<JMethod> entryList : program.entryMethods) {
+ for (JMethod entry : entryList) {
+ cfa.traverseFrom(entry);
+ }
+ }
+
deps.endDependencyGraph();
deps.close();
}
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 17e7b1f..9e27444 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
@@ -25,7 +25,9 @@
import com.google.gwt.dev.jjs.ast.js.JsCastMap;
import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.jjs.impl.CodeSplitter;
+import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement;
import com.google.gwt.dev.util.collect.Lists;
+import com.google.gwt.dev.util.collect.Maps;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -196,7 +198,7 @@
}
public static String getJsniSig(JMethod method, boolean addReturnType) {
- StringBuilder sb = new StringBuilder();
+ StringBuffer sb = new StringBuffer();
sb.append(method.getName());
sb.append("(");
for (int i = 0; i < method.getOriginalParamTypes().size(); ++i) {
@@ -301,6 +303,14 @@
public final List<JClassType> codeGenTypes = new ArrayList<JClassType>();
+ /**
+ * There is a list containing the main entry methods as well as the entry
+ * methods for each split point. The main entry methods are at entry 0 of this
+ * list. Split points are numbered sequentially from 1, and the entry methods
+ * for split point <em>i</em> are at entry <em>i</em> of this list.
+ */
+ public final List<List<JMethod>> entryMethods = new ArrayList<List<JMethod>>();
+
public final JTypeOracle typeOracle = new JTypeOracle(this);
/**
@@ -317,8 +327,6 @@
*/
private final CorrelationFactory correlator;
- private final List<JMethod> entryMethods = new ArrayList<JMethod>();
-
private final Map<String, JField> indexedFields = new HashMap<String, JField>();
private final Map<String, JMethod> indexedMethods = new HashMap<String, JMethod>();
@@ -332,7 +340,7 @@
/**
* Filled in by ReplaceRunAsync, once the numbers are known.
*/
- private List<JRunAsync> runAsyncs = Lists.create();
+ private Map<Integer, RunAsyncReplacement> runAsyncReplacements = Maps.create();
private List<Integer> splitPointInitialSequence = Lists.create();
@@ -377,8 +385,18 @@
}
public void addEntryMethod(JMethod entryPoint) {
- assert !entryMethods.contains(entryPoint);
- entryMethods.add(entryPoint);
+ addEntryMethod(entryPoint, 0);
+ }
+
+ public void addEntryMethod(JMethod entryPoint, int fragmentNumber) {
+ assert entryPoint.isStatic();
+ while (fragmentNumber >= entryMethods.size()) {
+ entryMethods.add(new ArrayList<JMethod>());
+ }
+ List<JMethod> methods = entryMethods.get(fragmentNumber);
+ if (!methods.contains(entryPoint)) {
+ methods.add(entryPoint);
+ }
}
public JClassType createClass(SourceInfo info, String name, boolean isAbstract, boolean isFinal) {
@@ -716,6 +734,14 @@
return result;
}
+ public List<JMethod> getAllEntryMethods() {
+ List<JMethod> allEntryMethods = new ArrayList<JMethod>();
+ for (List<JMethod> entries : entryMethods) {
+ allEntryMethods.addAll(entries);
+ }
+ return allEntryMethods;
+ }
+
public JsCastMap getCastMap(JReferenceType referenceType) {
// ensure jsonCastableTypeMaps has been initialized
// it might not have been if the CastNormalizer has not been run
@@ -733,13 +759,12 @@
return allTypes;
}
- public List<JMethod> getEntryMethods() {
- return entryMethods;
+ public int getEntryCount(int fragment) {
+ return entryMethods.get(fragment).size();
}
public int getFragmentCount() {
- // Initial fragment is the +1.
- return runAsyncs.size() + 1;
+ return entryMethods.size();
}
public JDeclaredType getFromTypeMap(String qualifiedBinaryOrSourceName) {
@@ -839,8 +864,8 @@
return integer.intValue();
}
- public List<JRunAsync> getRunAsyncs() {
- return runAsyncs;
+ public Map<Integer, RunAsyncReplacement> getRunAsyncReplacements() {
+ return runAsyncReplacements;
}
public List<Integer> getSplitPointInitialSequence() {
@@ -1012,8 +1037,9 @@
this.typesByQueryId = typesByQueryId;
}
- public void setRunAsyncs(List<JRunAsync> runAsyncs) {
- this.runAsyncs = Lists.normalizeUnmodifiable(runAsyncs);
+ public void setRunAsyncReplacements(Map<Integer, RunAsyncReplacement> map) {
+ assert runAsyncReplacements.isEmpty();
+ runAsyncReplacements = map;
}
public void setSplitPointInitialSequence(List<Integer> list) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JRunAsync.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JRunAsync.java
deleted file mode 100644
index b8d6844..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JRunAsync.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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;
-
-/**
- * Represents a GWT.runAsync() call.
- */
-public class JRunAsync extends JExpression {
-
- private final String name;
- private final JExpression onSuccessCall;
- private JExpression runAsyncCall;
- private final int splitPoint;
-
- public JRunAsync(SourceInfo info, int splitPoint, String name, JExpression runAsyncCall,
- JExpression onSuccessCall) {
- super(info);
- this.splitPoint = splitPoint;
- assert name != null;
- this.name = name;
- this.runAsyncCall = runAsyncCall;
- this.onSuccessCall = onSuccessCall;
- }
-
- /**
- * Based on either explicit class literal, or the jsni name of the containing
- * method.
- */
- public String getName() {
- return name;
- }
-
- /**
- * Returns a call expression akin to {@code callback.onSuccess()}.
- * {@link com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer} makes a synthetic
- * visit to this call on the "far" side of the split point, ie, the code that
- * runs when the fragment is through downloading.
- */
- public JExpression getOnSuccessCall() {
- return onSuccessCall;
- }
-
- /**
- * Returns a call expression akin to
- * {@code AsyncFragmentLoader.runAsync(7, callback)}. This represents the
- * "near" side of the split point, calling into the machinery that queues up
- * the fragment download.
- */
- public JExpression getRunAsyncCall() {
- return runAsyncCall;
- }
-
- /**
- * Returns the split point number, 1-based.
- */
- public int getSplitPoint() {
- return splitPoint;
- }
-
- @Override
- public JType getType() {
- return JPrimitiveType.VOID;
- }
-
- @Override
- public boolean hasSideEffects() {
- return true;
- }
-
- public void traverse(JVisitor visitor, Context ctx) {
- if (visitor.visit(this, ctx)) {
- /*
- * Normal code flow treats this node like the "near" side call into
- * AsyncFragmentLoader. We only visit the onSuccessCall "far" side
- * explicitly.
- */
- runAsyncCall = visitor.accept(runAsyncCall);
- }
- visitor.endVisit(this, ctx);
- }
-}
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..d653233 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
@@ -413,10 +413,6 @@
endVisit((JStatement) x, ctx);
}
- public void endVisit(JRunAsync x, Context ctx) {
- endVisit((JExpression) x, ctx);
- }
-
public void endVisit(JsCastMap x, Context ctx) {
endVisit((JsonArray) x, ctx);
}
@@ -737,10 +733,6 @@
return visit((JStatement) x, ctx);
}
- public boolean visit(JRunAsync x, Context ctx) {
- return visit((JExpression) 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/CloneExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
index 628841c..b953a47 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
@@ -44,7 +44,6 @@
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
-import com.google.gwt.dev.jjs.ast.JRunAsync;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JVisitor;
@@ -225,16 +224,6 @@
}
@Override
- public boolean visit(JRunAsync x, Context ctx) {
- // Only the runAsync call itself needs cloning, the onSuccess can be shared.
- JExpression runAsyncCall = cloneExpression(x.getRunAsyncCall());
- expression =
- new JRunAsync(x.getSourceInfo(), x.getSplitPoint(), x.getName(), runAsyncCall, x
- .getOnSuccessCall());
- return false;
- }
-
- @Override
public boolean visit(JMultiExpression x, Context ctx) {
JMultiExpression multi = new JMultiExpression(x.getSourceInfo());
multi.exprs.addAll(cloneExpressions(x.exprs));
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 647b8f4..9add414 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
@@ -34,7 +34,6 @@
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.JRunAsync;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer.DependencyRecorder;
@@ -42,6 +41,7 @@
import com.google.gwt.dev.jjs.impl.FragmentExtractor.LivenessPredicate;
import com.google.gwt.dev.jjs.impl.FragmentExtractor.NothingAlivePredicate;
import com.google.gwt.dev.jjs.impl.FragmentExtractor.StatementLogger;
+import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
@@ -247,7 +247,7 @@
public static void exec(TreeLogger logger, JProgram jprogram, JsProgram jsprogram,
JavaToJavaScriptMap map, MultipleDependencyGraphRecorder dependencyRecorder) {
- if (jprogram.getRunAsyncs().size() == 0) {
+ if (jprogram.entryMethods.size() == 1) {
// Don't do anything if there is no call to runAsync
return;
}
@@ -266,7 +266,9 @@
throws UnableToCompleteException {
Event codeSplitterEvent =
SpeedTracerLogger.start(CompilerEventType.CODE_SPLITTER, "phase", "findSplitPoint");
- Map<String, List<Integer>> nameToSplitPoint = reverseByName(program.getRunAsyncs());
+ Map<JMethod, List<Integer>> methodToSplitPoint =
+ reverseByEnclosingMethod(program.getRunAsyncReplacements());
+ Map<String, List<Integer>> nameToSplitPoint = reverseByName(program.getRunAsyncReplacements());
if (refString.startsWith("@")) {
JsniRef jsniRef = JsniRef.parse(refString);
@@ -295,8 +297,7 @@
}
JMethod method = (JMethod) referent;
- String canonicalName = ReplaceRunAsyncs.getImplicitName(method);
- List<Integer> splitPoints = nameToSplitPoint.get(canonicalName);
+ List<Integer> splitPoints = methodToSplitPoint.get(method);
if (splitPoints == null) {
branch.log(TreeLogger.ERROR, "Method does not enclose a runAsync call: " + jsniRef);
throw new UnableToCompleteException();
@@ -450,7 +451,7 @@
ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram);
cfa.setDependencyRecorder(dependencyRecorder);
- cfa.traverseEntryMethods();
+ traverseEntry(jprogram, cfa, 0);
traverseClassArray(jprogram, cfa);
dependencyRecorder.endDependencyGraph();
@@ -537,9 +538,29 @@
logger.log(TreeLogger.TRACE, message.toString());
}
- private static Map<String, List<Integer>> reverseByName(List<JRunAsync> runAsyncs) {
+ /**
+ * Reverses a runAsync map, returning a map from methods to the split point
+ * numbers invoked from within that method.
+ */
+ private static Map<JMethod, List<Integer>> reverseByEnclosingMethod(
+ Map<Integer, RunAsyncReplacement> runAsyncMap) {
+ Map<JMethod, List<Integer>> revmap = new HashMap<JMethod, List<Integer>>();
+ for (RunAsyncReplacement replacement : runAsyncMap.values()) {
+ JMethod method = replacement.getEnclosingMethod();
+ List<Integer> list = revmap.get(method);
+ if (list == null) {
+ list = new ArrayList<Integer>();
+ revmap.put(method, list);
+ }
+ list.add(replacement.getNumber());
+ }
+ return revmap;
+ }
+
+ private static Map<String, List<Integer>> reverseByName(
+ Map<Integer, RunAsyncReplacement> runAsyncReplacements) {
Map<String, List<Integer>> revmap = new HashMap<String, List<Integer>>();
- for (JRunAsync replacement : runAsyncs) {
+ for (RunAsyncReplacement replacement : runAsyncReplacements.values()) {
String name = replacement.getName();
if (name != null) {
List<Integer> list = revmap.get(name);
@@ -547,7 +568,7 @@
list = new ArrayList<Integer>();
revmap.put(name, list);
}
- list.add(replacement.getSplitPoint());
+ list.add(replacement.getNumber());
}
}
return revmap;
@@ -574,6 +595,16 @@
}
}
+ /**
+ * Traverse all code in the program that is reachable via split point
+ * <code>splitPoint</code>.
+ */
+ private static void traverseEntry(JProgram jprogram, ControlFlowAnalyzer cfa, int splitPoint) {
+ for (JMethod entryMethod : jprogram.entryMethods.get(splitPoint)) {
+ cfa.traverseFrom(entryMethod);
+ }
+ }
+
private static <T> Set<T> union(Set<? extends T> set1, Set<? extends T> set2) {
Set<T> union = new HashSet<T>();
union.addAll(set1);
@@ -629,7 +660,7 @@
this.dependencyRecorder = dependencyRecorder;
this.initialLoadSequence = new LinkedHashSet<Integer>(jprogram.getSplitPointInitialSequence());
- numEntries = jprogram.getRunAsyncs().size() + 1;
+ numEntries = jprogram.entryMethods.size();
logging = Boolean.getBoolean(PROP_LOG_FRAGMENT_MAP);
fieldToLiteralOfClass = buildFieldToClassLiteralMap(jprogram);
fragmentExtractor = new FragmentExtractor(jprogram, jsprogram, map);
@@ -673,26 +704,21 @@
private List<ControlFlowAnalyzer> computeAllButOneCfas() {
String dependencyGraphNameAfterInitialSequence = dependencyGraphNameAfterInitialSequence();
- List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>();
- for (JRunAsync runAsync : jprogram.getRunAsyncs()) {
- int splitPoint = runAsync.getSplitPoint();
- if (isInitial(splitPoint)) {
+ List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>(numEntries - 1);
+
+ for (int entry = 1; entry < numEntries; entry++) {
+ if (isInitial(entry)) {
allButOnes.add(null);
continue;
}
- dependencyRecorder.startDependencyGraph("sp" + splitPoint,
- dependencyGraphNameAfterInitialSequence);
+ dependencyRecorder
+ .startDependencyGraph("sp" + entry, dependencyGraphNameAfterInitialSequence);
ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(liveAfterInitialSequence);
cfa.setDependencyRecorder(dependencyRecorder);
- for (JRunAsync otherRunAsync : jprogram.getRunAsyncs()) {
- if (isInitial(otherRunAsync.getSplitPoint())) {
- continue;
- }
- if (otherRunAsync == runAsync) {
- continue;
- }
- cfa.traverseFromRunAsync(otherRunAsync);
- }
+ traverseAllButEntry(cfa, entry);
+ // Traverse leftoversFragmentHasLoaded, because it should not
+ // go into any of the exclusive fragments.
+ cfa.traverseFromLeftoversFragmentHasLoaded();
dependencyRecorder.endDependencyGraph();
allButOnes.add(cfa);
}
@@ -707,8 +733,10 @@
dependencyRecorder.startDependencyGraph("total", null);
ControlFlowAnalyzer everything = new ControlFlowAnalyzer(jprogram);
everything.setDependencyRecorder(dependencyRecorder);
- everything.traverseEntryMethods();
- everything.traverseFromRunAsyncs();
+ for (int entry = 0; entry < numEntries; entry++) {
+ traverseEntry(everything, entry);
+ }
+ everything.traverseFromLeftoversFragmentHasLoaded();
dependencyRecorder.endDependencyGraph();
return everything;
}
@@ -766,14 +794,12 @@
extendsCfa = depGraphName;
ControlFlowAnalyzer liveAfterSp = new ControlFlowAnalyzer(liveAfterInitialSequence);
- JRunAsync runAsync = jprogram.getRunAsyncs().get(sp - 1);
- assert runAsync.getSplitPoint() == sp;
- liveAfterSp.traverseFromRunAsync(runAsync);
+ traverseEntry(liveAfterSp, sp);
dependencyRecorder.endDependencyGraph();
LivenessPredicate liveNow = new CfaLivenessPredicate(liveAfterSp);
- List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(sp);
+ List<JsStatement> statsToAppend = fragmentExtractor.createCallsToEntryMethods(sp);
addFragment(sp, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
@@ -786,14 +812,13 @@
* Compute the exclusively live fragments. Each includes everything
* exclusively live after entry point i.
*/
- for (JRunAsync runAsync : jprogram.getRunAsyncs()) {
- int i = runAsync.getSplitPoint();
+ for (int i = 1; i < numEntries; i++) {
if (isInitial(i)) {
continue;
}
LivenessPredicate alreadyLoaded = new ExclusivityMapLivenessPredicate(fragmentMap, 0);
LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, i);
- List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(i);
+ List<JsStatement> statsToAppend = fragmentExtractor.createCallsToEntryMethods(i);
addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
}
@@ -803,7 +828,7 @@
{
LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(liveAfterInitialSequence);
LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, 0);
- List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(numEntries);
+ List<JsStatement> statsToAppend = fragmentExtractor.createCallToLeftoversFragmentHasLoaded();
addFragment(numEntries, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
}
@@ -969,19 +994,17 @@
}
allFields.addAll(everything.getFieldsWritten());
- for (JRunAsync runAsync : jprogram.getRunAsyncs()) {
- int splitPoint = runAsync.getSplitPoint();
- if (isInitial(splitPoint)) {
+ for (int entry = 1; entry < numEntries; entry++) {
+ if (isInitial(entry)) {
continue;
}
- ControlFlowAnalyzer allButOne = allButOnes.get(splitPoint - 1);
+ ControlFlowAnalyzer allButOne = allButOnes.get(entry - 1);
Set<JNode> allLiveNodes =
union(allButOne.getLiveFieldsAndMethods(), allButOne.getFieldsWritten());
- updateMap(splitPoint, fragmentMap.fields, allLiveNodes, allFields);
- updateMap(splitPoint, fragmentMap.methods, allButOne.getLiveFieldsAndMethods(), allMethods);
- updateMap(splitPoint, fragmentMap.strings, allButOne.getLiveStrings(), everything
- .getLiveStrings());
- updateMap(splitPoint, fragmentMap.types, declaredTypesIn(allButOne.getInstantiatedTypes()),
+ updateMap(entry, fragmentMap.fields, allLiveNodes, allFields);
+ updateMap(entry, fragmentMap.methods, allButOne.getLiveFieldsAndMethods(), allMethods);
+ updateMap(entry, fragmentMap.strings, allButOne.getLiveStrings(), everything.getLiveStrings());
+ updateMap(entry, fragmentMap.types, declaredTypesIn(allButOne.getInstantiatedTypes()),
declaredTypesIn(everything.getInstantiatedTypes()));
}
}
@@ -1000,4 +1023,21 @@
(new StringFinder()).accept(exp);
return strings;
}
+
+ /**
+ * Traverse all code in the program except for that reachable only via
+ * fragment <code>frag</code>. This does not call
+ * {@link ControlFlowAnalyzer#finishTraversal()}.
+ */
+ private void traverseAllButEntry(ControlFlowAnalyzer cfa, int entry) {
+ for (int otherEntry = 0; otherEntry < numEntries; otherEntry++) {
+ if (otherEntry != entry) {
+ traverseEntry(cfa, otherEntry);
+ }
+ }
+ }
+
+ private void traverseEntry(ControlFlowAnalyzer cfa, int splitPoint) {
+ traverseEntry(jprogram, cfa, splitPoint);
+ }
}
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 38568a2..591b3ae 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
@@ -42,7 +42,6 @@
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.JRunAsync;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JVariable;
@@ -90,7 +89,6 @@
*/
private class RescueVisitor extends JVisitor {
private final ArrayList<JMethod> curMethodStack = new ArrayList<JMethod>();
- private JMethod currentMethod;
@Override
public boolean visit(JArrayType type, Context ctx) {
@@ -368,21 +366,6 @@
@Override
public boolean visit(JMethodCall call, Context ctx) {
JMethod method = call.getTarget();
- if (method == runAsyncOnsuccess) {
- if (currentMethod != null
- && currentMethod.getEnclosingType() == program.getIndexedType("AsyncFragmentLoader")) {
- /*
- * Magic magic magic: don't allow code flow from the
- * AsyncFragmentLoader implementation back into the
- * callback.onSuccess(). If we did, the rescue path would look like
- * JRunAsync -> AsyncFragmentLoader.runAsync() ->
- * callback.onSuccess(). This would completely defeat code splitting
- * as all the code on the other side of the barrier would become
- * reachable.
- */
- return true;
- }
- }
if (method.isStatic() || program.isJavaScriptObject(method.getEnclosingType())
|| instantiatedTypes.contains(method.getEnclosingType())) {
rescue(method);
@@ -565,10 +548,7 @@
curMethodStack.add(method);
dependencyRecorder.methodIsLiveBecause(method, curMethodStack);
}
- JMethod lastMethod = currentMethod;
- currentMethod = method;
accept(method);
- currentMethod = lastMethod;
if (dependencyRecorder != null) {
curMethodStack.remove(curMethodStack.size() - 1);
}
@@ -811,9 +791,9 @@
*/
private Map<JParameter, List<JExpression>> argsToRescueIfParameterRead;
- private final JMethod asyncFragmentOnLoad;
private final JDeclaredType baseArrayType;
private DependencyRecorder dependencyRecorder;
+
private Set<JField> fieldsWritten = new HashSet<JField>();
private Set<JReferenceType> instantiatedTypes = new HashSet<JReferenceType>();
private Set<JNode> liveFieldsAndMethods = new HashSet<JNode>();
@@ -834,15 +814,13 @@
private Map<JMethod, List<JMethod>> methodsThatOverrideMe;
private final JProgram program;
+
private Set<JReferenceType> referencedTypes = new HashSet<JReferenceType>();
private final RescueVisitor rescuer = new RescueVisitor();
- private final JMethod runAsyncOnsuccess;
private JMethod stringValueOfChar = null;
public ControlFlowAnalyzer(ControlFlowAnalyzer cfa) {
program = cfa.program;
- asyncFragmentOnLoad = cfa.asyncFragmentOnLoad;
- runAsyncOnsuccess = cfa.runAsyncOnsuccess;
baseArrayType = cfa.baseArrayType;
fieldsWritten = new HashSet<JField>(cfa.fieldsWritten);
instantiatedTypes = new HashSet<JReferenceType>(cfa.instantiatedTypes);
@@ -861,8 +839,6 @@
public ControlFlowAnalyzer(JProgram program) {
this.program = program;
- asyncFragmentOnLoad = program.getIndexedMethod("AsyncFragmentLoader.onLoad");
- runAsyncOnsuccess = program.getIndexedMethod("RunAsyncCallback.onSuccess");
baseArrayType = program.getIndexedType("Array");
buildMethodsOverriding();
}
@@ -917,26 +893,10 @@
}
/**
- * Traverse the program entry points, but don't traverse any runAsync
- * fragments.
+ * Traverse all code executed by <code>expr</code>.
*/
- public void traverseEntryMethods() {
- for (JMethod method : program.getEntryMethods()) {
- traverseFrom(method);
- }
- if (program.getRunAsyncs().size() > 0) {
- /*
- * Explicitly rescue AsyncFragmentLoader.onLoad(). It is never explicitly
- * called anyway, until late code gen. Also, we want it in the initial
- * fragment so all other fragments can share the code.
- */
- traverseFrom(asyncFragmentOnLoad);
- /*
- * Keep callback.onSuccess() from being pruned since we explicitly avoid
- * visiting it.
- */
- liveFieldsAndMethods.add(runAsyncOnsuccess);
- }
+ public void traverseFrom(JExpression expr) {
+ rescuer.accept(expr);
}
/**
@@ -954,26 +914,17 @@
rescuer.rescue(type, true, true);
}
+ public void traverseFromLeftoversFragmentHasLoaded() {
+ if (program.entryMethods.size() > 1) {
+ traverseFrom(program
+ .getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded"));
+ }
+ }
+
public void traverseFromReferenceTo(JDeclaredType type) {
rescuer.rescue(type, true, false);
}
- /**
- * Traverse the fragment for a specific runAsync.
- */
- public void traverseFromRunAsync(JRunAsync runAsync) {
- rescuer.accept(runAsync.getOnSuccessCall());
- }
-
- /**
- * 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()) {
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..72cbd99 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
@@ -34,7 +34,6 @@
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;
@@ -191,6 +190,8 @@
return map.vtableInitToMethod(stat);
}
+ private Set<JsName> entryMethodNames;
+
private final JProgram jprogram;
private final JsProgram jsprogram;
@@ -207,18 +208,38 @@
this.jprogram = jprogram;
this.jsprogram = jsprogram;
this.map = map;
+
+ buildEntryMethodSet();
}
/**
- * Create a call to {@link AsyncFragmentLoader#onLoad}.
+ * Add direct calls to the entry methods of the specified entry number.
*/
- public List<JsStatement> createOnLoadedCall(int splitPoint) {
- JMethod loadMethod = jprogram.getIndexedMethod("AsyncFragmentLoader.onLoad");
- JsName loadMethodName = map.nameForMethod(loadMethod);
+ public List<JsStatement> createCallsToEntryMethods(int splitPoint) {
+ List<JsStatement> callStats = new ArrayList<JsStatement>(jprogram.entryMethods.size());
+ for (JMethod entryMethod : jprogram.entryMethods.get(splitPoint)) {
+ JsName name = map.nameForMethod(entryMethod);
+ assert name != null;
+ SourceInfo sourceInfo = jsprogram.getSourceInfo();
+ JsInvocation call = new JsInvocation(sourceInfo);
+ call.setQualifier(wrapWithEntry(name.makeRef(sourceInfo)));
+ callStats.add(call.makeStmt());
+ }
+ return callStats;
+ }
+
+ /**
+ * Create a call to
+ * {@link com.google.gwt.core.client.impl.AsyncFragmentLoader#leftoversFragmentHasLoaded()}
+ * .
+ */
+ public List<JsStatement> createCallToLeftoversFragmentHasLoaded() {
+ JMethod loadedMethod =
+ jprogram.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded");
+ JsName loadedMethodName = map.nameForMethod(loadedMethod);
SourceInfo sourceInfo = jsprogram.getSourceInfo();
JsInvocation call = new JsInvocation(sourceInfo);
- call.setQualifier(wrapWithEntry(loadMethodName.makeRef(sourceInfo)));
- call.getArguments().add(new JsNumberLiteral(sourceInfo, splitPoint));
+ call.setQualifier(wrapWithEntry(loadedMethodName.makeRef(sourceInfo)));
List<JsStatement> newStats = Collections.<JsStatement> singletonList(call.makeStmt());
return newStats;
}
@@ -239,41 +260,45 @@
*/
JClassType currentVtableType = null;
- // 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) {
- stat = result;
- keepIt = true;
+ for (int frag = 0; frag < jsprogram.getFragmentCount(); frag++) {
+ List<JsStatement> stats = jsprogram.getFragmentBlock(frag).getStatements();
+ for (JsStatement stat : stats) {
+ if (isEntryCall(stat)) {
+ continue;
+ }
+
+ boolean keepIt;
+ JClassType vtableTypeAssigned = vtableTypeAssigned(stat);
+ if (vtableTypeAssigned != null && livenessPredicate.isLive(vtableTypeAssigned)) {
+ JsExprStmt result =
+ extractPrototypeSetup(livenessPredicate, alreadyLoadedPredicate, stat,
+ vtableTypeAssigned);
+ if (result != null) {
+ stat = result;
+ keepIt = true;
+ } else {
+ keepIt = false;
+ }
+ } else if (containsRemovableVars(stat)) {
+ stat = removeSomeVars((JsVars) stat, livenessPredicate, alreadyLoadedPredicate);
+ keepIt = !(stat instanceof JsEmpty);
} else {
- keepIt = false;
+ keepIt = isLive(stat, livenessPredicate) && !isLive(stat, alreadyLoadedPredicate);
}
- } else if (containsRemovableVars(stat)) {
- stat = removeSomeVars((JsVars) stat, livenessPredicate, alreadyLoadedPredicate);
- keepIt = !(stat instanceof JsEmpty);
- } else {
- keepIt = isLive(stat, livenessPredicate) && !isLive(stat, alreadyLoadedPredicate);
- }
- statementLogger.logStatement(stat, keepIt);
+ statementLogger.logStatement(stat, keepIt);
- if (keepIt) {
- if (vtableTypeAssigned != null) {
- currentVtableType = vtableTypeAssigned;
+ if (keepIt) {
+ if (vtableTypeAssigned != null) {
+ currentVtableType = vtableTypeAssigned;
+ }
+ JClassType vtableType = vtableTypeNeeded(stat);
+ if (vtableType != null && vtableType != currentVtableType) {
+ extractedStats.add(vtableStatFor(vtableType));
+ currentVtableType = vtableType;
+ }
+ extractedStats.add(stat);
}
- JClassType vtableType = vtableTypeNeeded(stat);
- if (vtableType != null && vtableType != currentVtableType) {
- extractedStats.add(vtableStatFor(vtableType));
- currentVtableType = vtableType;
- }
- extractedStats.add(stat);
}
}
@@ -302,6 +327,23 @@
statementLogger = logger;
}
+ private void buildEntryMethodSet() {
+ entryMethodNames = new HashSet<JsName>();
+ for (JMethod entryMethod : jprogram.getAllEntryMethods()) {
+ JsName name = map.nameForMethod(entryMethod);
+ assert name != null;
+ entryMethodNames.add(name);
+ }
+
+ JMethod leftoverFragmentLoaded =
+ jprogram.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded");
+ if (leftoverFragmentLoaded != null) {
+ JsName name = map.nameForMethod(leftoverFragmentLoaded);
+ assert name != null;
+ entryMethodNames.add(name);
+ }
+ }
+
/**
* Check whether this statement is a <code>JsVars</code> that contains
* individual vars that could be removed. If it does, then
@@ -374,6 +416,27 @@
return result;
}
+ /**
+ * Check whether the statement invokes an entry method. Detect JavaScript code
+ * of the form foo() where foo is a the JavaScript function corresponding to
+ * an entry method.
+ */
+ private boolean isEntryCall(JsStatement stat) {
+ if (stat instanceof JsExprStmt) {
+ JsExpression expr = ((JsExprStmt) stat).getExpression();
+ if (expr instanceof JsInvocation) {
+ JsInvocation inv = (JsInvocation) expr;
+ if (inv.getArguments().isEmpty() && (inv.getQualifier() instanceof JsNameRef)) {
+ JsNameRef calleeRef = (JsNameRef) inv.getQualifier();
+ if (calleeRef.getQualifier() == null) {
+ return entryMethodNames.contains(calleeRef.getName());
+ }
+ }
+ }
+ }
+ return false;
+ }
+
private boolean isLive(JsStatement stat, LivenessPredicate livenessPredicate) {
JClassType type = map.typeForStatement(stat);
if (type != null) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
new file mode 100644
index 0000000..6bfa2de
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
+import com.google.gwt.dev.cfg.StaticPropertyOracle;
+import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.jdt.FindDeferredBindingSitesVisitor;
+
+import java.io.PrintWriter;
+
+/**
+ * Generates code for loading an island. The pattern of generated classes is
+ * more complicated than otherwise necessary so that the loader code is
+ * precisely handled by TypeTightener and LivenessAnalyzer.
+ *
+ * TODO(spoon) Remove this generator by making LivenessAnalyzer know about
+ * runAsync and how it works.
+ */
+public class FragmentLoaderCreator {
+ public static final String ASYNC_FRAGMENT_LOADER =
+ "com.google.gwt.core.client.impl.AsyncFragmentLoader";
+ public static final String ASYNC_LOADER_CLASS_PREFIX = "AsyncLoader";
+ public static final String ASYNC_LOADER_PACKAGE = "com.google.gwt.lang.asyncloaders";
+ public static final String BROWSER_LOADER = "AsyncFragmentLoader.BROWSER_LOADER";
+ public static final String CALLBACK_LIST_SUFFIX = "__Callback";
+ public static final String LOADER_METHOD_RUN_ASYNC = "runAsync";
+ public static final String RUN_ASYNC_CALLBACK = "com.google.gwt.core.client.RunAsyncCallback";
+ public static final String RUN_CALLBACKS = "runCallbacks";
+ private static final String GWT_CLASS = FindDeferredBindingSitesVisitor.MAGIC_CLASS;
+ private static final String PROP_RUN_ASYNC_NEVER_RUNS = "gwt.jjs.runAsyncNeverRuns";
+ private static final String UNCAUGHT_EXCEPTION_HANDLER_CLASS = "GWT.UncaughtExceptionHandler";
+
+ private final StandardGeneratorContext context;
+ private int entryNumber = 0;
+ private final PropertyOracle propOracle;
+
+ /**
+ * Construct a FragmentLoaderCreator. The reason it needs so many parameters
+ * is that it uses generator infrastructure.
+ */
+ public FragmentLoaderCreator(StandardGeneratorContext context) {
+ // An empty property oracle is fine, because fragment loaders aren't
+ // affected by properties anyway
+ this.propOracle =
+ new StaticPropertyOracle(new BindingProperty[0], new String[0],
+ new ConfigurationProperty[0]);
+ this.context = context;
+ }
+
+ public String create(TreeLogger logger) throws UnableToCompleteException {
+ // First entry is 1.
+ ++entryNumber;
+ context.setPropertyOracle(propOracle);
+ PrintWriter loaderWriter = getSourceWriterForLoader(logger, context);
+ if (loaderWriter == null) {
+ logger.log(TreeLogger.ERROR, "Failed to create island loader named "
+ + getLoaderQualifiedName());
+ throw new UnableToCompleteException();
+ }
+
+ generateLoaderFields(loaderWriter);
+ generateOnLoadMethod(loaderWriter);
+ generateRunAsyncMethod(loaderWriter);
+ generateRunCallbacksMethod(loaderWriter);
+ generateRunCallbackOnFailuresMethod(loaderWriter);
+
+ loaderWriter.println("}");
+ loaderWriter.close();
+ context.commit(logger, loaderWriter);
+
+ writeCallbackListClass(logger, context);
+
+ return getLoaderQualifiedName();
+ }
+
+ private void generateLoaderFields(PrintWriter srcWriter) {
+ srcWriter.println("// Callbacks that are pending");
+ srcWriter.println("private static " + getCallbackListSimpleName() + " callbacksHead = null;");
+
+ srcWriter.println("// The tail of the callbacks list");
+ srcWriter.println("private static " + getCallbackListSimpleName() + " callbacksTail = null;");
+
+ srcWriter.println("// A callback caller for this entry point");
+ srcWriter.println("private static " + getLoaderSimpleName() + " instance = null;");
+ }
+
+ private void generateOnLoadMethod(PrintWriter srcWriter) {
+ srcWriter.println("public static void onLoad() {");
+ srcWriter.println("instance = new " + getLoaderSimpleName() + "();");
+ srcWriter.println(BROWSER_LOADER + ".fragmentHasLoaded(" + entryNumber + ");");
+
+ srcWriter.println(BROWSER_LOADER + ".logEventProgress(\"" + RUN_CALLBACKS + entryNumber
+ + "\", \"begin\");");
+ srcWriter.println("instance." + RUN_CALLBACKS + "();");
+ srcWriter.println(BROWSER_LOADER + ".logEventProgress(\"" + RUN_CALLBACKS + entryNumber
+ + "\", \"end\");");
+
+ srcWriter.println("}");
+ }
+
+ /**
+ * Generate the <code>runAsync</code> method. Calls to
+ * <code>GWT.runAsync</code> are replaced by calls to this method.
+ */
+ private void generateRunAsyncMethod(PrintWriter srcWriter) {
+ srcWriter.println("public static void " + LOADER_METHOD_RUN_ASYNC
+ + "(RunAsyncCallback callback) {");
+ srcWriter.println(getCallbackListSimpleName() + " newCallback = new "
+ + getCallbackListSimpleName() + "();");
+ srcWriter.println("newCallback.callback = callback;");
+
+ srcWriter.println("if (callbacksTail != null) {");
+ srcWriter.println(" callbacksTail.next = newCallback;");
+ srcWriter.println("}");
+
+ srcWriter.println("callbacksTail = newCallback;");
+ srcWriter.println("if (callbacksHead == null) {");
+ srcWriter.println(" callbacksHead = newCallback;");
+ srcWriter.println("}");
+
+ srcWriter.println("if (instance != null) {");
+ srcWriter.println(" instance." + RUN_CALLBACKS + "();");
+ srcWriter.println(" return;");
+ srcWriter.println("}");
+ srcWriter.println("if (!" + BROWSER_LOADER + ".isLoading(" + entryNumber + ")) {");
+ srcWriter.println(" " + BROWSER_LOADER + ".inject(" + entryNumber + ",");
+ srcWriter.println(" new AsyncFragmentLoader.LoadTerminatedHandler() {");
+ srcWriter.println(" public void loadTerminated(Throwable reason) {");
+ srcWriter.println(" runCallbackOnFailures(reason);");
+ srcWriter.println(" }");
+ srcWriter.println(" });");
+ srcWriter.println("}");
+ srcWriter.println("}");
+ }
+
+ private void generateRunCallbackOnFailuresMethod(PrintWriter srcWriter) {
+ srcWriter.println("private static void runCallbackOnFailures(Throwable e) {");
+ srcWriter.println("while (callbacksHead != null) {");
+ srcWriter.println(" callbacksHead.callback.onFailure(e);");
+ srcWriter.println(" callbacksHead = callbacksHead.next;");
+ srcWriter.println("}");
+ srcWriter.println("callbacksTail = null;");
+ srcWriter.println("}");
+ }
+
+ private void generateRunCallbacksMethod(PrintWriter srcWriter) {
+ srcWriter.println("public void " + RUN_CALLBACKS + "() {");
+
+ srcWriter.println("while (callbacksHead != null) {");
+
+ srcWriter.println(" " + UNCAUGHT_EXCEPTION_HANDLER_CLASS + " handler = "
+ + "GWT.getUncaughtExceptionHandler();");
+
+ srcWriter.println(" " + getCallbackListSimpleName() + " next = callbacksHead;");
+ srcWriter.println(" callbacksHead = callbacksHead.next;");
+ srcWriter.println(" if (callbacksHead == null) {");
+ srcWriter.println(" callbacksTail = null;");
+ srcWriter.println(" }");
+
+ if (!Boolean.getBoolean(PROP_RUN_ASYNC_NEVER_RUNS)) {
+ // TODO(spoon): this runs the callbacks immediately; deferred would be
+ // better
+ srcWriter.println(" if (handler == null) {");
+ srcWriter.println(" next.callback.onSuccess();");
+ srcWriter.println(" } else {");
+ srcWriter.println(" try {");
+ srcWriter.println(" next.callback.onSuccess();");
+ srcWriter.println(" } catch (Throwable e) {");
+ srcWriter.println(" handler.onUncaughtException(e);");
+ srcWriter.println(" }");
+ srcWriter.println(" }");
+ }
+
+ srcWriter.println("}");
+ srcWriter.println("}");
+ }
+
+ private String getCallbackListQualifiedName() {
+ return ASYNC_LOADER_PACKAGE + getCallbackListSimpleName();
+ }
+
+ private String getCallbackListSimpleName() {
+ return getLoaderSimpleName() + CALLBACK_LIST_SUFFIX;
+ }
+
+ private String getLoaderQualifiedName() {
+ return ASYNC_LOADER_PACKAGE + "." + getLoaderSimpleName();
+ }
+
+ private String getLoaderSimpleName() {
+ return ASYNC_LOADER_CLASS_PREFIX + entryNumber;
+ }
+
+ private String getPackage() {
+ return ASYNC_LOADER_PACKAGE;
+ }
+
+ private PrintWriter getSourceWriterForLoader(TreeLogger logger, GeneratorContext ctx) {
+ PrintWriter printWriter = ctx.tryCreate(logger, getPackage(), getLoaderSimpleName());
+ if (printWriter == null) {
+ return null;
+ }
+
+ printWriter.println("package " + getPackage() + ";");
+ String[] imports = new String[]{GWT_CLASS, RUN_ASYNC_CALLBACK, ASYNC_FRAGMENT_LOADER};
+ for (String imp : imports) {
+ printWriter.println("import " + imp + ";");
+ }
+
+ printWriter.println("public class " + getLoaderSimpleName() + " {");
+ return printWriter;
+ }
+
+ private void writeCallbackListClass(TreeLogger logger, GeneratorContext ctx)
+ throws UnableToCompleteException {
+ PrintWriter printWriter = ctx.tryCreate(logger, getPackage(), getCallbackListSimpleName());
+ if (printWriter == null) {
+ logger.log(TreeLogger.ERROR, "Could not create type: " + getCallbackListQualifiedName());
+ throw new UnableToCompleteException();
+ }
+
+ printWriter.println("package " + getPackage() + ";");
+ printWriter.println("public class " + getCallbackListSimpleName() + "{");
+ printWriter.println(RUN_ASYNC_CALLBACK + " callback;");
+ printWriter.println(getCallbackListSimpleName() + " next;");
+ printWriter.println("}");
+
+ printWriter.close();
+ ctx.commit(logger, printWriter);
+ }
+}
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 1f552ec..ff3e1f7 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
@@ -150,11 +150,11 @@
import com.google.gwt.dev.js.ast.JsWhile;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.collect.IdentityHashSet;
-import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Maps;
import java.io.StringReader;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
@@ -585,14 +585,14 @@
/**
* The JavaScript functions corresponding to the entry methods of the
- * program ({@link JProgram#getEntryMethods()}).
+ * program ({@link JProgram#getAllEntryMethods()}).
*/
private JsFunction[] entryFunctions;
/**
* A reverse index for the entry methods of the program (
- * {@link JProgram#getEntryMethods()}). Each entry method is mapped to its
- * integer index.
+ * {@link JProgram#getAllEntryMethods()}). Each entry method is mapped to
+ * its integer index.
*/
private Map<JMethod, Integer> entryMethodToIndex;
@@ -1214,7 +1214,7 @@
List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements();
// Generate entry methods
- generateGwtOnLoad(Lists.create(entryFunctions), globalStmts);
+ generateGwtOnLoad(Arrays.asList(entryFunctions).subList(0, x.getEntryCount(0)), globalStmts);
// Add a few things onto the beginning.
@@ -1238,13 +1238,28 @@
}
}
- if (program.getRunAsyncs().size() > 0) {
- // Prevent onLoad from being pruned.
- JMethod onLoadMethod = program.getIndexedMethod("AsyncFragmentLoader.onLoad");
- JsName name = names.get(onLoadMethod);
- assert name != null;
- JsFunction func = (JsFunction) name.getStaticRef();
- func.setArtificiallyRescued(true);
+ /*
+ * Add calls to all non-initial entry points. That way, if the code
+ * splitter does not run, the resulting code will still function.
+ * Likewise, add a call to
+ * AsyncFragmentLoader.leftoversFragmentHasLoaded().
+ */
+ List<JsFunction> nonInitialEntries =
+ Arrays.asList(entryFunctions).subList(x.getEntryCount(0), entryFunctions.length);
+ if (!nonInitialEntries.isEmpty()) {
+ JMethod loadedMethod =
+ program.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded");
+ JsName loadedMethodName = names.get(loadedMethod);
+ JsInvocation call = new JsInvocation(jsProgram.getSourceInfo());
+ call.setQualifier(loadedMethodName.makeRef(jsProgram.getSourceInfo().makeChild()));
+ globalStmts.add(call.makeStmt());
+ }
+ for (JsFunction func : nonInitialEntries) {
+ if (func != null) {
+ JsInvocation call = new JsInvocation(jsProgram.getSourceInfo());
+ call.setQualifier(func.getName().makeRef(jsProgram.getSourceInfo().makeChild()));
+ globalStmts.add(call.makeStmt());
+ }
}
}
@@ -1407,7 +1422,7 @@
* Arrange for entryFunctions to be filled in as functions are visited.
* See their Javadoc comments for more details.
*/
- List<JMethod> entryMethods = x.getEntryMethods();
+ List<JMethod> entryMethods = x.getAllEntryMethods();
entryFunctions = new JsFunction[entryMethods.size()];
entryMethodToIndex = new IdentityHashMap<JMethod, Integer>();
for (int i = 0; i < entryMethods.size(); i++) {
@@ -1688,7 +1703,6 @@
JsName gwtOnLoadName = topScope.declareName("gwtOnLoad");
gwtOnLoadName.setObfuscatable(false);
JsFunction gwtOnLoad = new JsFunction(sourceInfo, topScope, gwtOnLoadName, true);
- gwtOnLoad.setArtificiallyRescued(true);
globalStmts.add(gwtOnLoad.makeStmt());
JsBlock body = new JsBlock(sourceInfo);
gwtOnLoad.setBody(body);
@@ -2036,7 +2050,7 @@
@Override
public void endVisit(JProgram x, Context ctx) {
// Entry methods can be called externally, so they must run clinit.
- crossClassTargets.addAll(x.getEntryMethods());
+ crossClassTargets.addAll(x.getAllEntryMethods());
}
@Override
@@ -2077,7 +2091,9 @@
@Override
public void endVisit(JProgram x, Context ctx) {
- Collections.sort(x.getEntryMethods(), hasNameSort);
+ for (List<JMethod> methods : x.entryMethods) {
+ Collections.sort(methods, hasNameSort);
+ }
Collections.sort(x.getDeclaredTypes(), hasNameSort);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java
index 825b433..6bef7d4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java
@@ -81,11 +81,6 @@
}
}
- if (functionIndices.size() < 2) {
- // No need to sort 0 or 1 functions.
- return;
- }
-
// sort the indices according to size of statement range
Collections.sort(functionIndices, new Comparator<Integer>() {
public int compare(Integer index1, Integer index2) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index 0c2cfd3..aef2ca7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -304,6 +304,17 @@
*/
return;
}
+
+ if (isRunCallbacksMethod(x.getTarget())) {
+ /*
+ * Don't devirtualize these calls created by FragmentLoaderCreator,
+ * because it spoils code splitting.
+ *
+ * TODO(spoon) remove this once FragmentLoaderCreator is gone
+ */
+ return;
+ }
+
ctx.replaceMe(makeStaticCall(x, newMethod));
}
@@ -355,6 +366,18 @@
}
}
+ private static boolean isRunCallbacksMethod(JMethod method) {
+ if (method.getEnclosingType() != null
+ && method.getEnclosingType().getName().startsWith(
+ FragmentLoaderCreator.ASYNC_LOADER_PACKAGE + "."
+ + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX)
+ && method.getName().equals(FragmentLoaderCreator.RUN_CALLBACKS)) {
+ return true;
+ }
+
+ return false;
+ }
+
protected Set<JMethod> toBeMadeStatic = new HashSet<JMethod>();
private final JProgram program;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
index 5f3f141..199ab83 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
@@ -99,6 +99,13 @@
return stats;
}
+ /**
+ * Tighten method calls that occur within <code>node</code> and its children.
+ */
+ public static void exec(JProgram program, JNode node) {
+ new MethodCallTightener(program).execImpl(node);
+ }
+
private final JProgram program;
private MethodCallTightener(JProgram program) {
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 9a74db2..5b37451 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
@@ -466,7 +466,7 @@
@Override
public boolean visit(JProgram program, Context ctx) {
- for (JMethod method : program.getEntryMethods()) {
+ for (JMethod method : program.getAllEntryMethods()) {
accept(method);
}
for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext();) {
@@ -607,8 +607,10 @@
*/
traverseFromCodeGenTypes(livenessAnalyzer);
}
- livenessAnalyzer.traverseEntryMethods();
- livenessAnalyzer.traverseFromRunAsyncs();
+ for (JMethod method : program.getAllEntryMethods()) {
+ livenessAnalyzer.traverseFrom(method);
+ }
+ livenessAnalyzer.traverseFromLeftoversFragmentHasLoaded();
program.typeOracle.setInstantiatedTypes(livenessAnalyzer.getInstantiatedTypes());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
index 2323b87..d0ae938 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
@@ -24,18 +24,17 @@
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JField;
-import com.google.gwt.dev.jjs.ast.JIntLiteral;
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.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
-import com.google.gwt.dev.jjs.ast.JRunAsync;
+import com.google.gwt.dev.jjs.ast.JType;
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.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -51,8 +50,63 @@
* by an equivalent call using an integer rather than a class literal.
*/
public class ReplaceRunAsyncs {
+ /**
+ * Information about the replacement of one runAsync call by a call to a
+ * generated code-loading method.
+ */
+ public static class RunAsyncReplacement implements Serializable {
+ private final JMethod enclosingMethod;
+ private final JMethod loadMethod;
+ private final String name;
+ private final int number;
+
+ RunAsyncReplacement(int number, JMethod enclosingMethod, JMethod loadMethod, String name) {
+ this.number = number;
+ this.enclosingMethod = enclosingMethod;
+ this.loadMethod = loadMethod;
+ this.name = name;
+ }
+
+ /**
+ * Can be null if the enclosing method cannot be designated with a JSNI
+ * reference.
+ */
+ public JMethod getEnclosingMethod() {
+ return enclosingMethod;
+ }
+
+ /**
+ * The load method to request loading the code for this method.
+ */
+ public JMethod getLoadMethod() {
+ return loadMethod;
+ }
+
+ /**
+ * Return the name of this runAsync, which is specified by a class literal
+ * in the two-argument version of runAsync(). Returns <code>null</code> if
+ * there is no name for the call.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The index of this runAsync, numbered from 1 to n.
+ */
+ public int getNumber() {
+ return number;
+ }
+
+ @Override
+ public String toString() {
+ return "#" + number + ": " + enclosingMethod.toString();
+ }
+ }
+
private class AsyncCreateVisitor extends JModVisitor {
private JMethod currentMethod;
+ private int entryCount = 1;
@Override
public void endVisit(JMethodCall x, Context ctx) {
@@ -62,17 +116,11 @@
String name;
switch (x.getArgs().size()) {
case 1:
- name = getImplicitName(currentMethod);
+ name = null;
asyncCallback = x.getArgs().get(0);
break;
case 2:
- JExpression arg0 = x.getArgs().get(0);
- if (!(arg0 instanceof JClassLiteral)) {
- error(arg0.getSourceInfo(),
- "Only class literals may be used to name a call to GWT.runAsync()");
- return;
- }
- name = nameFromClassLiteral((JClassLiteral) arg0);
+ name = nameFromClassLiteral((JClassLiteral) x.getArgs().get(0));
asyncCallback = x.getArgs().get(1);
break;
default:
@@ -80,30 +128,21 @@
"runAsync call found with neither 1 nor 2 arguments: " + x);
}
- int splitPoint = runAsyncs.size() + 1;
- SourceInfo info = x.getSourceInfo();
+ int entryNumber = entryCount++;
+ JClassType loader = getFragmentLoader(entryNumber);
+ JMethod loadMethod = getRunAsyncMethod(loader);
+ assert loadMethod != null;
+ runAsyncReplacements.put(entryNumber, new RunAsyncReplacement(entryNumber, currentMethod,
+ loadMethod, name));
- JMethod runAsyncMethod = program.getIndexedMethod("AsyncFragmentLoader.runAsync");
- assert runAsyncMethod != null;
- JMethodCall runAsyncCall = new JMethodCall(info, null, runAsyncMethod);
- runAsyncCall.addArg(JIntLiteral.get(splitPoint));
- runAsyncCall.addArg(asyncCallback);
+ JMethodCall methodCall = new JMethodCall(x.getSourceInfo(), null, loadMethod);
+ methodCall.addArg(asyncCallback);
- JReferenceType callbackType = (JReferenceType) asyncCallback.getType();
- callbackType = callbackType.getUnderlyingType();
- JMethod callbackMethod;
- if (callbackType instanceof JClassType) {
- callbackMethod =
- program.typeOracle.getPolyMethod((JClassType) callbackType, "onSuccess()V");
- } else {
- callbackMethod = program.getIndexedMethod("RunAsyncCallback.onSuccess");
- }
- assert callbackMethod != null;
- JMethodCall onSuccessCall = new JMethodCall(info, asyncCallback, callbackMethod);
+ tightenCallbackType(entryNumber, asyncCallback.getType());
- JRunAsync runAsyncNode = new JRunAsync(info, splitPoint, name, runAsyncCall, onSuccessCall);
- runAsyncs.add(runAsyncNode);
- ctx.replaceMe(runAsyncNode);
+ program.addEntryMethod(getOnLoadMethod(loader), entryNumber);
+
+ ctx.replaceMe(methodCall);
}
}
@@ -120,20 +159,62 @@
return method.getEnclosingType() == program.getIndexedType("GWT")
&& method.getName().equals("runAsync");
}
+
+ /**
+ * Tighten some types and method calls immediately, in case the optimizer is
+ * not run. Without a little bit of tightening, code splitting will be
+ * completely ineffective.
+ *
+ * Note that {@link FragmentLoaderCreator} can't simply generate the tighter
+ * types to begin with, because when it runs, it doesn't know which runAsync
+ * call it is generating a loader for.
+ *
+ * This method can be deleted if {@link FragmentLoaderCreator} is
+ * eliminated.
+ */
+ private void tightenCallbackType(int entryNumber, JType callbackType) {
+ JClassType loaderClass = getFragmentLoader(entryNumber);
+
+ /*
+ * Before: class AsyncLoader3 { static void runAsync(RunAsyncCallback cb)
+ * { ... } }
+ *
+ * After: class AsyncLoader3 { static void runAsync(RunAsyncCallback$3 cb)
+ * { ... } }
+ */
+ JMethod loadMethod = getRunAsyncMethod(loaderClass);
+ loadMethod.getParams().get(0).setType(callbackType);
+
+ /*
+ * Before: class AsyncLoader3__Callback { RunAsyncCallback callback; }
+ *
+ * After: class AsyncLoader3__Callback { RunAsyncCallback$3 callback; }
+ */
+ JClassType callbackListType = getFragmentLoaderCallbackList(entryNumber);
+ JField callbackField = getField(callbackListType, "callback");
+
+ /*
+ * The method AsyncLoaderNNN.runCallbacks has a lot of calls to onSuccess
+ * methods where the target is onSuccess in the RunAsyncCallback
+ * interface. Use MethodCallTightener to tighten those calls down to
+ * target the onSuccess method of a specific callback class.
+ */
+ callbackField.setType(callbackType);
+ JMethod runCallbacksMethod = getMethod(loaderClass, FragmentLoaderCreator.RUN_CALLBACKS);
+ MethodCallTightener.exec(program, runCallbacksMethod);
+ }
}
private class ReplaceRunAsyncResources extends JModVisitor {
- private final Map<String, List<JRunAsync>> replacementsByName;
- private final JMethod runAsyncCode;
+ private final Map<String, List<RunAsyncReplacement>> replacementsByName;
public ReplaceRunAsyncResources() {
- replacementsByName = new HashMap<String, List<JRunAsync>>();
- runAsyncCode = program.getIndexedMethod("RunAsyncCode.runAsyncCode");
- for (JRunAsync replacement : runAsyncs) {
+ replacementsByName = new HashMap<String, List<RunAsyncReplacement>>();
+ for (RunAsyncReplacement replacement : runAsyncReplacements.values()) {
String name = replacement.getName();
if (name != null) {
- List<JRunAsync> list = replacementsByName.get(name);
+ List<RunAsyncReplacement> list = replacementsByName.get(name);
if (list == null) {
- list = new ArrayList<JRunAsync>();
+ list = new ArrayList<RunAsyncReplacement>();
replacementsByName.put(name, list);
}
list.add(replacement);
@@ -143,7 +224,7 @@
@Override
public void endVisit(JMethodCall x, Context ctx) {
- if (x.getTarget() == runAsyncCode) {
+ if (x.getTarget() == program.getIndexedMethod("RunAsyncCode.runAsyncCode")) {
JExpression arg0 = x.getArgs().get(0);
if (!(arg0 instanceof JClassLiteral)) {
error(arg0.getSourceInfo(), "Only a class literal may be passed to runAsyncCode");
@@ -151,28 +232,42 @@
}
JClassLiteral lit = (JClassLiteral) arg0;
String name = nameFromClassLiteral(lit);
- List<JRunAsync> matches = replacementsByName.get(name);
- SourceInfo info = x.getSourceInfo();
+ List<RunAsyncReplacement> matches = replacementsByName.get(name);
if (matches == null || matches.size() == 0) {
- error(info, "No runAsync call is named " + name);
+ error(x.getSourceInfo(), "No runAsync call is named " + name);
return;
}
if (matches.size() > 1) {
- TreeLogger branch = error(info, "Multiple runAsync calls are named " + name);
- for (JRunAsync match : matches) {
- branch.log(TreeLogger.ERROR, "One call is at '" + match.getSourceInfo().getFileName()
- + ':' + match.getSourceInfo().getStartLine() + "'");
+ TreeLogger branch = error(x.getSourceInfo(), "Multiple runAsync calls are named " + name);
+ for (RunAsyncReplacement match : matches) {
+ branch.log(TreeLogger.ERROR, "One call is in "
+ + methodDescription(match.getEnclosingMethod()));
}
return;
}
- int splitPoint = matches.get(0).getSplitPoint();
+ Integer splitPoint = matches.get(0).getNumber();
+
JMethodCall newCall =
- new JMethodCall(info, null, program
+ new JMethodCall(x.getSourceInfo(), null, program
.getIndexedMethod("RunAsyncCode.forSplitPointNumber"));
newCall.addArg(program.getLiteralInt(splitPoint));
ctx.replaceMe(newCall);
}
}
+
+ private String methodDescription(JMethod method) {
+ StringBuilder desc = new StringBuilder();
+ desc.append(method.getEnclosingType().getName());
+ desc.append(".");
+ desc.append(method.getName());
+ desc.append(" (");
+ desc.append(method.getSourceInfo().getFileName());
+ desc.append(':');
+ desc.append(method.getSourceInfo().getStartLine());
+ desc.append(")");
+
+ return desc.toString();
+ }
}
public static void exec(TreeLogger logger, JProgram program) throws UnableToCompleteException {
@@ -195,15 +290,33 @@
return initializerCall;
}
- static String getImplicitName(JMethod method) {
- String name;
- StringBuilder sb = new StringBuilder();
- sb.append('@');
- sb.append(method.getEnclosingType().getName());
- sb.append("::");
- sb.append(JProgram.getJsniSig(method, false));
- name = sb.toString();
- return name;
+ private static JMethod getMethod(JClassType type, String name) {
+ for (JMethod method : type.getMethods()) {
+ if (method.getName().equals(name)) {
+ return method;
+ }
+ }
+ throw new InternalCompilerException("Method not found: " + type.getName() + "." + name);
+ }
+
+ private static JMethod getOnLoadMethod(JClassType loaderType) {
+ assert loaderType != null;
+ assert loaderType.getMethods() != null;
+ JMethod method = getMethod(loaderType, "onLoad");
+ assert method.isStatic();
+ assert method.getParams().size() == 0;
+ return method;
+ }
+
+ private static JMethod getRunAsyncMethod(JClassType loaderType) {
+ assert loaderType != null;
+ assert loaderType.getMethods() != null;
+ JMethod method = getMethod(loaderType, "runAsync");
+ assert (method.isStatic());
+ assert (method.getParams().size() == 1);
+ assert (method.getParams().get(0).getType().getName()
+ .equals(FragmentLoaderCreator.RUN_ASYNC_CALLBACK));
+ return method;
}
/**
@@ -217,7 +330,8 @@
private final TreeLogger logger;
private final JProgram program;
- private final List<JRunAsync> runAsyncs = new ArrayList<JRunAsync>();
+ private final Map<Integer, RunAsyncReplacement> runAsyncReplacements =
+ new HashMap<Integer, RunAsyncReplacement>();
private ReplaceRunAsyncs(TreeLogger logger, JProgram program) {
this.logger = logger;
@@ -227,7 +341,7 @@
private TreeLogger error(SourceInfo info, String message) {
errorsFound = true;
TreeLogger fileLogger =
- logger.branch(TreeLogger.ERROR, "Errors in '" + info.getFileName() + "'");
+ logger.branch(TreeLogger.ERROR, "Error in '" + info.getFileName() + "'");
String linePrefix = "";
if (info.getStartLine() > 0) {
linePrefix = "Line " + info.getStartLine() + ": ";
@@ -239,14 +353,43 @@
private void execImpl() throws UnableToCompleteException {
AsyncCreateVisitor visitor = new AsyncCreateVisitor();
visitor.accept(program);
- setNumEntriesInAsyncFragmentLoader(runAsyncs.size() + 1);
- program.setRunAsyncs(runAsyncs);
+ setNumEntriesInAsyncFragmentLoader(visitor.entryCount);
+ program.setRunAsyncReplacements(runAsyncReplacements);
new ReplaceRunAsyncResources().accept(program);
if (errorsFound) {
throw new UnableToCompleteException();
}
}
+ private JField getField(JClassType type, String name) {
+ for (JField field : type.getFields()) {
+ if (field.getName().equals(name)) {
+ return field;
+ }
+ }
+ throw new InternalCompilerException("Field not found: " + type.getName() + "." + name);
+ }
+
+ private JClassType getFragmentLoader(int fragmentNumber) {
+ String fragmentLoaderClassName =
+ FragmentLoaderCreator.ASYNC_LOADER_PACKAGE + "."
+ + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX + fragmentNumber;
+ JType result = program.getFromTypeMap(fragmentLoaderClassName);
+ assert (result != null);
+ assert (result instanceof JClassType);
+ return (JClassType) result;
+ }
+
+ private JClassType getFragmentLoaderCallbackList(int fragmentNumber) {
+ String className =
+ FragmentLoaderCreator.ASYNC_LOADER_PACKAGE + "."
+ + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX + fragmentNumber
+ + FragmentLoaderCreator.CALLBACK_LIST_SUFFIX;
+ JType result = program.getFromTypeMap(className);
+ assert (result != null);
+ return (JClassType) result;
+ }
+
private void setNumEntriesInAsyncFragmentLoader(int entryCount) {
JMethodCall constructorCall = getBrowserLoaderConstructor(program);
assert constructorCall.getArgs().get(0).getType() == JPrimitiveType.INT;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index 63ff5c6..016e0bf 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -674,7 +674,11 @@
if (refType instanceof JArrayType) {
JArrayType arrayType = (JArrayType) refType;
- refType = nullifyArrayType(arrayType);
+ JArrayType newArrayType = nullifyArrayType(arrayType);
+ if (arrayType != newArrayType) {
+ x.setType(newArrayType);
+ madeChanges();
+ }
}
// tighten based on leaf types
@@ -735,7 +739,7 @@
}
}
- if (x.getType() != resultType) {
+ if (refType != resultType) {
x.setType(resultType);
madeChanges();
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java b/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java
index 1083bba..042fdbf5 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java
@@ -25,17 +25,42 @@
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsVisitor;
-import com.google.gwt.dev.util.collect.IdentityHashSet;
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.Set;
+import java.util.HashMap;
+import java.util.Map;
/**
* Removes JsFunctions that are never referenced in the program.
*/
public class JsUnusedFunctionRemover {
+ /**
+ * Finds all functions in the program.
+ */
+ private class JsFunctionVisitor extends JsVisitor {
+
+ @Override
+ public void endVisit(JsFunction x, JsContext ctx) {
+ // Anonymous function, ignore it
+ if (x.getName() != null && !x.isArtificiallyRescued()) {
+ toRemove.put(x.getName(), x);
+ }
+ }
+ }
+
+ /**
+ * Finds all function references in the program.
+ */
+ private class JsNameRefVisitor extends JsVisitor {
+
+ @Override
+ public void endVisit(JsNameRef x, JsContext ctx) {
+ toRemove.remove(x.getName());
+ }
+ }
+
private class RemovalVisitor extends JsModVisitor {
@Override
@@ -47,29 +72,22 @@
JsFunction f = (JsFunction) x.getExpression();
JsName name = f.getName();
- // Anonymous function, ignore it
- if (name == null || seen.contains(name) || f.isArtificiallyRescued()) {
- return;
+ if (toRemove.containsKey(name)) {
+ // Removing a static initializer indicates a problem in
+ // JsInliner.
+ if (name.getIdent().equals("$clinit")) {
+ throw new InternalCompilerException("Tried to remove clinit "
+ + name.getStaticRef().toSource());
+ }
+
+ if (!name.isObfuscatable()) {
+ // This is intended to be used externally (e.g. gwtOnLoad)
+ return;
+ }
+
+ // Remove the statement
+ ctx.removeMe();
}
-
- // Removing a static initializer indicates a problem in JsInliner.
- if (f.getExecuteOnce()) {
- throw new InternalCompilerException("Tried to remove clinit "
- + name.getStaticRef().toSource());
- }
- // Remove the statement
- ctx.removeMe();
- }
- }
-
- /**
- * Finds all function references in the program.
- */
- private class RescueVisitor extends JsVisitor {
-
- @Override
- public void endVisit(JsNameRef x, JsContext ctx) {
- seen.add(x.getName());
}
}
@@ -84,7 +102,7 @@
}
private final JsProgram program;
- private final Set<JsName> seen = new IdentityHashSet<JsName>();
+ private final Map<JsName, JsFunction> toRemove = new HashMap<JsName, JsFunction>();
public JsUnusedFunctionRemover(JsProgram program) {
this.program = program;
@@ -93,8 +111,11 @@
public OptimizerStats execImpl() {
OptimizerStats stats = new OptimizerStats(NAME);
- // Rescue all referenced functions.
- new RescueVisitor().accept(program);
+ // Find all functions
+ (new JsFunctionVisitor()).accept(program);
+
+ // Remove the functions that are referenced from the hit list
+ (new JsNameRefVisitor()).accept(program);
// Remove the unused functions from the JsProgram
RemovalVisitor removalVisitor = new RemovalVisitor();
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 85487b2..98c3c23 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -51,26 +51,6 @@
*/
public class JavaAstConstructor {
- public static final MockJavaResource ASYNCFRAGMENTLOADER = new MockJavaResource(
- "com.google.gwt.core.client.impl.AsyncFragmentLoader") {
- @Override
- public CharSequence getContent() {
- StringBuffer code = new StringBuffer();
- code.append("package com.google.gwt.core.client.impl;\n");
- code.append("import com.google.gwt.core.client.RunAsyncCallback;\n");
- code.append("public class AsyncFragmentLoader {\n");
- code.append(" public static void onLoad(int fragment) { }\n");
- code.append(" public static void runAsync(int fragment, RunAsyncCallback callback) { }\n");
- code.append(" public static AsyncFragmentLoader BROWSER_LOADER =\n");
- code.append(" makeBrowserLoader(1, new int[] {});\n");
- code.append(" private static AsyncFragmentLoader makeBrowserLoader(\n");
- code.append(" int numSp, int[] initial) {\n");
- code.append(" return null;\n");
- code.append(" }\n");
- code.append("}\n");
- return code;
- }
- };
public static final MockJavaResource ARRAY = new MockJavaResource("com.google.gwt.lang.Array") {
@Override
public CharSequence getContent() {
@@ -173,23 +153,7 @@
public CharSequence getContent() {
StringBuffer code = new StringBuffer();
code.append("package com.google.gwt.core.client;\n");
- code.append("public interface RunAsyncCallback {\n");
- code.append(" void onSuccess();\n");
- code.append("}\n");
- return code;
- }
- };
- public static final MockJavaResource RUNASYNCCODE = new MockJavaResource(
- "com.google.gwt.core.client.prefetch.RunAsyncCode") {
- @Override
- public CharSequence getContent() {
- StringBuffer code = new StringBuffer();
- code.append("package com.google.gwt.core.client.prefetch;\n");
- code.append("public class RunAsyncCode {\n");
- code.append(" public static RunAsyncCode runAsyncCode(Class<?> splitPoint) {\n");
- code.append(" return null;\n");
- code.append(" }");
- code.append("}");
+ code.append("public interface RunAsyncCallback { }\n");
return code;
}
};
@@ -291,8 +255,7 @@
// Replace the basic Class and Enum with a compiler-specific one.
result.remove(JavaResourceBase.CLASS);
result.remove(JavaResourceBase.ENUM);
- Collections.addAll(result, ASYNCFRAGMENTLOADER, ARRAY, CAST, CLASS, CLASSLITERALHOLDER, ENUM,
- GWT, RUNASYNCCALLBACK, RUNASYNCCODE);
+ Collections.addAll(result, ARRAY, CAST, CLASS, CLASSLITERALHOLDER, ENUM, GWT, RUNASYNCCALLBACK);
return result.toArray(new MockJavaResource[result.size()]);
}
}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncsErrorMessagesTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncsErrorMessagesTest.java
index f8f9808..d91d4d2 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncsErrorMessagesTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncsErrorMessagesTest.java
@@ -50,25 +50,101 @@
return code;
}
});
-
addSnippetImport("test.SplitPoint3");
- expectError("Line 8: Multiple runAsync calls are named test.SplitPoint1");
- expectError("One call is at '/mock/test/SplitPoint1.java:5'");
- expectError("One call is at '/mock/test/SplitPoint3.java:5'");
+
+ expectError("Line 15: Multiple runAsync calls are named test.SplitPoint1");
+ expectError("One call is in test.SplitPoint1.doStuff (/mock/test/SplitPoint1.java:4)");
+ expectError("One call is in test.SplitPoint3.doStuff (/mock/test/SplitPoint3.java:4)");
+
testSnippet("RunAsyncCode.runAsyncCode(SplitPoint1.class);");
}
public void testNonClassLiteral() {
- expectError("Line 7: Only a class literal may be passed to runAsyncCode");
+ expectError("Line 14: Only a class literal may be passed to runAsyncCode");
testSnippet("RunAsyncCode.runAsyncCode(new SplitPoint1().getClass());");
}
public void testNonExistentSplitPoint() {
- expectError("Line 7: No runAsync call is named java.lang.String");
+ expectError("Line 14: No runAsync call is named java.lang.String");
testSnippet("RunAsyncCode.runAsyncCode(String.class);");
}
+ private void addAsyncLoader(final int sp) {
+ sourceOracle.addOrReplace(new MockJavaResource("com.google.gwt.lang.asyncloaders.AsyncLoader"
+ + sp) {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.lang.asyncloaders;\n");
+ code.append("import com.google.gwt.core.client.RunAsyncCallback;");
+ code.append("public class AsyncLoader" + sp + " {\n");
+ code.append(" public static void onLoad() { }\n");
+ code.append(" public static void runAsync(RunAsyncCallback cb) { }\n");
+ code.append(" public static void runCallbacks() { }\n");
+ code.append("}\n");
+ return code;
+ }
+ });
+
+ addSnippetImport("com.google.gwt.lang.asyncloaders.AsyncLoader" + sp);
+
+ sourceOracle.addOrReplace(new MockJavaResource("com.google.gwt.lang.asyncloaders.AsyncLoader"
+ + sp + FragmentLoaderCreator.CALLBACK_LIST_SUFFIX) {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.lang.asyncloaders;\n");
+ code.append("import com.google.gwt.core.client.RunAsyncCallback;");
+ code.append("public class AsyncLoader" + sp + FragmentLoaderCreator.CALLBACK_LIST_SUFFIX
+ + "{\n");
+ code.append(" RunAsyncCallback callback;\n");
+ code.append("}\n");
+ return code;
+ }
+ });
+
+ addSnippetImport("com.google.gwt.lang.asyncloaders.AsyncLoader" + sp
+ + FragmentLoaderCreator.CALLBACK_LIST_SUFFIX);
+ }
+
private void addCommonTestCode() {
+ addAsyncLoader(1);
+ addAsyncLoader(2);
+ addAsyncLoader(3);
+
+ sourceOracle.addOrReplace(new MockJavaResource(
+ "com.google.gwt.core.client.impl.AsyncFragmentLoader") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.core.client.impl;\n");
+ code.append("public class AsyncFragmentLoader {\n");
+ code.append(" private static AsyncFragmentLoader BROWSER_LOADER =\n");
+ code.append(" makeBrowserLoader(1, new int[] {});\n");
+ code.append(" private static AsyncFragmentLoader makeBrowserLoader(\n");
+ code.append(" int numSp, int[] initial) {\n");
+ code.append(" return null;\n");
+ code.append(" }\n");
+ code.append("}\n");
+ return code;
+ }
+ });
+ addSnippetImport("com.google.gwt.core.client.impl.AsyncFragmentLoader");
+
+ sourceOracle.addOrReplace(new MockJavaResource(
+ "com.google.gwt.core.client.prefetch.RunAsyncCode") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.core.client.prefetch;\n");
+ code.append("public class RunAsyncCode {\n");
+ code.append(" public static RunAsyncCode runAsyncCode(Class<?> splitPoint) {\n");
+ code.append(" return null;\n");
+ code.append(" }");
+ code.append("}");
+ return code;
+ }
+ });
addSnippetImport("com.google.gwt.core.client.prefetch.RunAsyncCode");
sourceOracle.addOrReplace(new MockJavaResource("test.SplitPoint1") {
@@ -111,7 +187,7 @@
private void initializeTestLoggerBuilder() {
testLoggerBuilder = new UnitTestTreeLogger.Builder();
testLoggerBuilder.setLowestLogLevel(TreeLogger.ERROR);
- expectError("Errors in '/mock/test/EntryPoint.java'");
+ expectError("Error in '/mock/test/EntryPoint.java'");
}
private void testSnippet(String codeSnippet) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java
index 77de550..e35dd65 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java
@@ -18,7 +18,6 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
-import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.util.UnitTestTreeLogger;
/**
@@ -62,14 +61,14 @@
builder.expectError("Errors in '/mock/test/EntryPoint.java'", null);
builder.expectError(
"Line 5: Only class literals may be used to name a call to GWT.runAsync()", null);
+ builder.expectError("Cannot proceed due to previous errors", null);
logger = builder.createLogger();
this.logger = logger;
}
addSnippetImport("com.google.gwt.core.client.GWT");
try {
- JProgram program = compileSnippet("void", "GWT.runAsync((new Object()).getClass(), null);");
- ReplaceRunAsyncs.exec(logger, program);
+ compileSnippet("void", "GWT.runAsync((new Object()).getClass(), null);");
fail("Expected compilation to fail");
} catch (UnableToCompleteException e) {
// expected
diff --git a/tools/api-checker/config/gwt23_24userApi.conf b/tools/api-checker/config/gwt23_24userApi.conf
index 3e00070..88547b8 100644
--- a/tools/api-checker/config/gwt23_24userApi.conf
+++ b/tools/api-checker/config/gwt23_24userApi.conf
@@ -116,8 +116,7 @@
##############################################
#excluded packages colon separated list
-excludedPackages com.google.gwt.core.client.impl\
-:com.google.gwt.editor.client.impl\
+excludedPackages com.google.gwt.editor.client.impl\
:com.google.gwt.junit.client.impl\
:com.google.gwt.benchmarks.client.impl\
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index 4e66b6c..8861641 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.core.client;
+import com.google.gwt.core.client.impl.AsyncFragmentLoader;
import com.google.gwt.core.client.impl.Impl;
/**
@@ -245,14 +246,14 @@
*/
@SuppressWarnings("unused") // parameter will be used following replacement
public static void runAsync(Class<?> name, RunAsyncCallback callback) {
- callback.onSuccess();
+ runAsyncWithoutCodeSplitting(callback);
}
/**
* Run the specified callback once the necessary code for it has been loaded.
*/
public static void runAsync(RunAsyncCallback callback) {
- callback.onSuccess();
+ runAsyncWithoutCodeSplitting(callback);
}
/**
@@ -282,4 +283,36 @@
private static native String getVersion0() /*-{
return $gwt_version;
}-*/;
+
+ /**
+ * This implementation of runAsync simply calls the callback. It is only used
+ * when no code splitting has occurred.
+ */
+ private static void runAsyncWithoutCodeSplitting(RunAsyncCallback callback) {
+ /*
+ * By default, just call the callback. This allows using
+ * <code>runAsync</code> in code that might or might not run in a web
+ * browser.
+ */
+ if (isScript()) {
+ /*
+ * It's possible that the code splitter does not run, even for a
+ * production build. Signal a lightweight event, anyway, just so that
+ * there isn't a complete lack of lightweight events for runAsync.
+ */
+ AsyncFragmentLoader.BROWSER_LOADER.logEventProgress("noDownloadNeeded", "begin");
+ AsyncFragmentLoader.BROWSER_LOADER.logEventProgress("noDownloadNeeded", "end");
+ }
+
+ UncaughtExceptionHandler handler = sUncaughtExceptionHandler;
+ if (handler == null) {
+ callback.onSuccess();
+ } else {
+ try {
+ callback.onSuccess();
+ } catch (Throwable e) {
+ handler.onUncaughtException(e);
+ }
+ }
+ }
}
diff --git a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
index a86590c..c95c5ea 100644
--- a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.RunAsyncCallback;
/**
* <p>
@@ -266,22 +265,13 @@
public static AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{});
/**
- * Called by compiler-generated code when a fragment is loaded.
- *
- * @param fragment the fragment number
+ * A helper static method that invokes
+ * BROWSER_LOADER.leftoversFragmentHasLoaded(). Such a call is generated by
+ * the compiler, as it is much simpler if there is a static method to wrap up
+ * the call.
*/
- public static void onLoad(int fragment) {
- BROWSER_LOADER.onLoadImpl(fragment);
- }
-
- /**
- * Called by the compiler to implement {@link GWT#runAsync}.
- *
- * @param fragment the fragment number
- * @param callback the callback to run
- */
- public static void runAsync(int fragment, RunAsyncCallback callback) {
- BROWSER_LOADER.runAsyncImpl(fragment, callback);
+ public static void browserLoaderLeftoversFragmentHasLoaded() {
+ BROWSER_LOADER.leftoversFragmentHasLoaded();
}
/**
@@ -301,11 +291,6 @@
}
/**
- * Callbacks indexed by fragment number.
- */
- private final RunAsyncCallback[][] allCallbacks;
-
- /**
* The fragment currently loading, or -1 if there aren't any.
*/
private int fragmentLoading = -1;
@@ -373,46 +358,15 @@
this.loadingStrategy = loadingStrategy;
this.logger = logger;
int numEntriesPlusOne = numEntries + 1;
- this.allCallbacks = new RunAsyncCallback[numEntriesPlusOne][];
- this.requestedExclusives = new BoundedIntQueue(numEntriesPlusOne);
- this.isLoaded = new boolean[numEntriesPlusOne];
- this.pendingDownloadErrorHandlers = new LoadTerminatedHandler[numEntriesPlusOne];
- }
-
- public boolean isAlreadyLoaded(int splitPoint) {
- return isLoaded[splitPoint];
- }
-
- /**
- * Request that a sequence of split points be prefetched. Code for the split
- * points in <code>splitPoints</code> will be downloaded and installed
- * whenever there is nothing else to download. Each call to this method
- * overwrites the entire prefetch queue with the newly specified one.
- */
- public void setPrefetchQueue(int... runAsyncSplitPoints) {
- if (prefetchQueue == null) {
- prefetchQueue = new BoundedIntQueue(numEntries);
- }
- prefetchQueue.clear();
- for (int sp : runAsyncSplitPoints) {
- prefetchQueue.add(sp);
- }
- startLoadingNextFragment();
- }
-
- public void startPrefetching() {
- prefetching = true;
- startLoadingNextFragment();
- }
-
- public void stopPrefetching() {
- prefetching = false;
+ requestedExclusives = new BoundedIntQueue(numEntriesPlusOne);
+ isLoaded = new boolean[numEntriesPlusOne];
+ pendingDownloadErrorHandlers = new LoadTerminatedHandler[numEntriesPlusOne];
}
/**
* Inform the loader that a fragment has now finished loading.
*/
- void fragmentHasLoaded(int fragment) {
+ public void fragmentHasLoaded(int fragment) {
logFragmentLoaded(fragment);
if (fragment < pendingDownloadErrorHandlers.length) {
pendingDownloadErrorHandlers[fragment] = null;
@@ -441,7 +395,7 @@
*
* @param splitPoint the split point whose code needs to be loaded
*/
- void inject(int splitPoint, LoadTerminatedHandler loadErrorHandler) {
+ public void inject(int splitPoint, LoadTerminatedHandler loadErrorHandler) {
pendingDownloadErrorHandlers[splitPoint] = loadErrorHandler;
if (!isInitial(splitPoint)) {
requestedExclusives.add(splitPoint);
@@ -449,8 +403,49 @@
startLoadingNextFragment();
}
- void leftoversFragmentHasLoaded() {
- onLoadImpl(leftoversFragment());
+ public boolean isAlreadyLoaded(int splitPoint) {
+ return isLoaded[splitPoint];
+ }
+
+ public boolean isLoading(int splitPoint) {
+ return pendingDownloadErrorHandlers[splitPoint] != null;
+ }
+
+ public void leftoversFragmentHasLoaded() {
+ fragmentHasLoaded(leftoversFragment());
+ }
+
+ /**
+ * Log an event with the {@Logger} this instance was provided.
+ */
+ public void logEventProgress(String eventGroup, String type) {
+ logEventProgress(eventGroup, type, -1, -1);
+ }
+
+ /**
+ * Request that a sequence of split points be prefetched. Code for the split
+ * points in <code>splitPoints</code> will be downloaded and installed
+ * whenever there is nothing else to download. Each call to this method
+ * overwrites the entire prefetch queue with the newly specified one.
+ */
+ public void setPrefetchQueue(int... runAsyncSplitPoints) {
+ if (prefetchQueue == null) {
+ prefetchQueue = new BoundedIntQueue(numEntries);
+ }
+ prefetchQueue.clear();
+ for (int sp : runAsyncSplitPoints) {
+ prefetchQueue.add(sp);
+ }
+ startLoadingNextFragment();
+ }
+
+ public void startPrefetching() {
+ prefetching = true;
+ startLoadingNextFragment();
+ }
+
+ public void stopPrefetching() {
+ prefetching = false;
}
private boolean anyPrefetchesRequested() {
@@ -527,10 +522,6 @@
return false;
}
- private boolean isLoading(int splitPoint) {
- return pendingDownloadErrorHandlers[splitPoint] != null;
- }
-
private int leftoversFragment() {
return numEntries;
}
@@ -540,13 +531,6 @@
}
/**
- * Log an event with the {@Logger} this instance was provided.
- */
- private void logEventProgress(String eventGroup, String type) {
- logEventProgress(eventGroup, type, -1, -1);
- }
-
- /**
* Log event progress via the {@link Logger} this instance was provided. The
* <code>fragment</code> and <code>size</code> objects are allowed to be
* <code>null</code>.
@@ -560,58 +544,6 @@
logEventProgress(logGroup, LwmLabels.END, fragment, -1);
}
- private void onLoadImpl(int fragment) {
- fragmentHasLoaded(fragment);
- RunAsyncCallback[] callbacks = allCallbacks[fragment];
- if (callbacks != null) {
- logEventProgress("runCallbacks" + fragment, "begin");
- allCallbacks[fragment] = null;
- GWT.UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
- for (RunAsyncCallback callback : callbacks) {
- if (handler == null) {
- callback.onSuccess();
- } else {
- try {
- callback.onSuccess();
- } catch (Throwable e) {
- handler.onUncaughtException(e);
- }
- }
- }
- logEventProgress("runCallbacks" + fragment, "end");
- }
- }
-
- private void runAsyncImpl(final int fragment, RunAsyncCallback callback) {
- if (isLoaded[fragment]) {
- assert allCallbacks[fragment] == null;
- callback.onSuccess();
- return;
- }
-
- RunAsyncCallback[] callbacks = allCallbacks[fragment];
- if (callbacks == null) {
- callbacks = allCallbacks[fragment] = new RunAsyncCallback[0];
- }
- // Take advantage of no range checking in web mode.
- assert GWT.isScript();
- callbacks[callbacks.length] = callback;
-
- if (!isLoading(fragment)) {
- inject(fragment, new AsyncFragmentLoader.LoadTerminatedHandler() {
- public void loadTerminated(Throwable reason) {
- RunAsyncCallback[] callbacks = allCallbacks[fragment];
- if (callbacks != null) {
- allCallbacks[fragment] = null;
- for (RunAsyncCallback callback : callbacks) {
- callback.onFailure(reason);
- }
- }
- }
- });
- }
- }
-
private void startLoadingFragment(int fragment) {
assert (fragmentLoading < 0);
fragmentLoading = fragment;
diff --git a/user/src/com/google/gwt/core/client/prefetch/RunAsyncCode.java b/user/src/com/google/gwt/core/client/prefetch/RunAsyncCode.java
index 5fa8101..7d9c4be 100644
--- a/user/src/com/google/gwt/core/client/prefetch/RunAsyncCode.java
+++ b/user/src/com/google/gwt/core/client/prefetch/RunAsyncCode.java
@@ -57,7 +57,7 @@
* Ask whether this code has already been loaded.
*/
public boolean isLoaded() {
- if (!GWT.isScript()) {
+ if (GWT.isScript()) {
return true;
}
return AsyncFragmentLoader.BROWSER_LOADER.isAlreadyLoaded(splitPoint);
diff --git a/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java b/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
index d47185f..44a5adf 100644
--- a/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
@@ -116,10 +116,11 @@
// save the original handler
final UncaughtExceptionHandler originalHandler = GWT.getUncaughtExceptionHandler();
- // set a handler that looks for toThrow
- GWT.UncaughtExceptionHandler myHandler = new GWT.UncaughtExceptionHandler() {
+ // set a handler that catches toThrow and nothing else
+ GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable e) {
GWT.setUncaughtExceptionHandler(originalHandler);
+
if (e == toThrow) {
// expected
finishTest();
@@ -128,22 +129,17 @@
throw new RuntimeException(e);
}
}
- };
- GWT.setUncaughtExceptionHandler(myHandler);
+ });
+
delayTestFinish(RUNASYNC_TIMEOUT);
- try {
- GWT.runAsync(new RunAsyncCallback() {
- public void onFailure(Throwable caught) {
- }
+ GWT.runAsync(new RunAsyncCallback() {
+ public void onFailure(Throwable caught) {
+ }
- public void onSuccess() {
- throw toThrow;
- }
- });
- } catch (Throwable e) {
- // runAsync can either throw immediately, or throw uncaught.
- myHandler.onUncaughtException(e);
- }
+ public void onSuccess() {
+ throw toThrow;
+ }
+ });
}
}