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; + } + }); } }