Re-implement runAsync to improve code size.

The new formulation doesn't try to tickle optimizers so much, and as a result can share a lot more code than the old implementation.

http://gwt-code-reviews.appspot.com/1442807/


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10216 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 d44153b..3432dd3 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,6 +19,7 @@
 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;
@@ -112,10 +113,14 @@
 
     try {
       printPre();
-      for (JMethod method : jprogram.getAllEntryMethods()) {
+      for (JMethod method : jprogram.getEntryMethods()) {
         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 8630ef4..bb507f8 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,17 +16,15 @@
 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.impl.ReplaceRunAsyncs.RunAsyncReplacement;
+import com.google.gwt.dev.jjs.ast.JRunAsync;
 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.Map;
+import java.util.List;
 import java.util.zip.GZIPOutputStream;
 
 /**
@@ -55,21 +53,21 @@
       htmlOut.indentIn();
       htmlOut.indentIn();
 
-      Map<Integer, String> splitPointMap = splitPointNames(jprogram);
-      if (splitPointMap.size() > 0) {
+      List<JRunAsync> runAsyncs = jprogram.getRunAsyncs();
+      if (runAsyncs.size() > 0) {
         curLine = "<splitpoints>";
         htmlOut.printRaw(curLine);
         htmlOut.newline();
         htmlOut.indentIn();
         htmlOut.indentIn();
-        for (int sp = 1; sp <= splitPointMap.size(); sp++) {
-          String location = splitPointMap.get(sp);
-          assert location != null;
-          curLine = "<splitpoint id=\"" + sp + "\" location=\"" + location + "\"/>";
+        for (JRunAsync runAsync : runAsyncs) {
+          int sp = runAsync.getSplitPoint();
+          String name = runAsync.getName();
+          curLine = "<splitpoint id=\"" + sp + "\" location=\"" + name + "\"/>";
           htmlOut.printRaw(curLine);
           htmlOut.newline();
           if (logger.isLoggable(TreeLogger.TRACE)) {
-            logger.log(TreeLogger.TRACE, "Assigning split point #" + sp + " in method " + location);
+            logger.log(TreeLogger.TRACE, "Assigning split point #" + sp + " for '" + name + "'");
           }
         }
         htmlOut.indentOut();
@@ -113,37 +111,6 @@
     }
   }
 
-  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 6f03ac9..fe180a1 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
@@ -25,10 +25,8 @@
 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;
 
 /**
@@ -54,7 +52,6 @@
     }
   }
 
-  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";
 
@@ -68,8 +65,6 @@
 
   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) {
@@ -78,10 +73,8 @@
     }
 
     String methodName = String.valueOf(messageSend.selector);
-    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.
+    if (!methodName.equals(REBIND_MAGIC_METHOD)) {
+      // Not the create() method.
       return;
     }
 
@@ -95,33 +88,13 @@
     MessageSendSite site = new MessageSendSite(messageSend, scope);
 
     Expression[] args = messageSend.arguments;
-    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.length != 1) {
+      reportRebindProblem(site, "GWT.create() should take exactly one argument");
+      return;
     }
 
-    if (asyncMagicMethod) {
-      runAsyncCalls.add(new MessageSendSite(messageSend, scope));
+    if (!(args[0] instanceof ClassLiteralAccess)) {
+      reportRebindProblem(site, "Only class literals may be used as arguments to GWT.create()");
       return;
     }
 
@@ -133,13 +106,6 @@
     }
   }
 
-  /**
-   * 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 aa43758..3492108 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
@@ -19,7 +19,6 @@
 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;
@@ -57,19 +56,14 @@
     return results;
   }
 
-  private final FragmentLoaderCreator fragmentLoaderCreator;
   private final RebindPermutationOracle rebindPermOracle;
 
   /**
-   * 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.
+   * Construct a WebModeCompilerFrontEnd.
    */
   private WebModeCompilerFrontEnd(RebindPermutationOracle rebindPermOracle, TypeLinker linker) {
     super(rebindPermOracle.getCompilationState(), linker);
     this.rebindPermOracle = rebindPermOracle;
-    this.fragmentLoaderCreator = new FragmentLoaderCreator(rebindPermOracle.getGeneratorContext());
   }
 
   @Override
@@ -110,26 +104,6 @@
       }
     }
 
-    /*
-     * 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 0845492..4336215 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -80,7 +80,6 @@
 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;
@@ -516,7 +515,6 @@
     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.
@@ -1295,12 +1293,7 @@
 
     ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(program);
     cfa.setDependencyRecorder(deps);
-    for (List<JMethod> entryList : program.entryMethods) {
-      for (JMethod entry : entryList) {
-        cfa.traverseFrom(entry);
-      }
-    }
-
+    cfa.traverseEntryMethods();
     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 9e27444..17e7b1f 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,9 +25,7 @@
 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;
@@ -198,7 +196,7 @@
   }
 
   public static String getJsniSig(JMethod method, boolean addReturnType) {
-    StringBuffer sb = new StringBuffer();
+    StringBuilder sb = new StringBuilder();
     sb.append(method.getName());
     sb.append("(");
     for (int i = 0; i < method.getOriginalParamTypes().size(); ++i) {
@@ -303,14 +301,6 @@
 
   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);
 
   /**
@@ -327,6 +317,8 @@
    */
   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>();
@@ -340,7 +332,7 @@
   /**
    * Filled in by ReplaceRunAsync, once the numbers are known.
    */
-  private Map<Integer, RunAsyncReplacement> runAsyncReplacements = Maps.create();
+  private List<JRunAsync> runAsyncs = Lists.create();
 
   private List<Integer> splitPointInitialSequence = Lists.create();
 
@@ -385,18 +377,8 @@
   }
 
   public void addEntryMethod(JMethod 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);
-    }
+    assert !entryMethods.contains(entryPoint);
+    entryMethods.add(entryPoint);
   }
 
   public JClassType createClass(SourceInfo info, String name, boolean isAbstract, boolean isFinal) {
@@ -734,14 +716,6 @@
     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
@@ -759,12 +733,13 @@
     return allTypes;
   }
 
-  public int getEntryCount(int fragment) {
-    return entryMethods.get(fragment).size();
+  public List<JMethod> getEntryMethods() {
+    return entryMethods;
   }
 
   public int getFragmentCount() {
-    return entryMethods.size();
+    // Initial fragment is the +1.
+    return runAsyncs.size() + 1;
   }
 
   public JDeclaredType getFromTypeMap(String qualifiedBinaryOrSourceName) {
@@ -864,8 +839,8 @@
     return integer.intValue();
   }
 
-  public Map<Integer, RunAsyncReplacement> getRunAsyncReplacements() {
-    return runAsyncReplacements;
+  public List<JRunAsync> getRunAsyncs() {
+    return runAsyncs;
   }
 
   public List<Integer> getSplitPointInitialSequence() {
@@ -1037,9 +1012,8 @@
     this.typesByQueryId = typesByQueryId;
   }
 
-  public void setRunAsyncReplacements(Map<Integer, RunAsyncReplacement> map) {
-    assert runAsyncReplacements.isEmpty();
-    runAsyncReplacements = map;
+  public void setRunAsyncs(List<JRunAsync> runAsyncs) {
+    this.runAsyncs = Lists.normalizeUnmodifiable(runAsyncs);
   }
 
   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
new file mode 100644
index 0000000..b8d6844
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JRunAsync.java
@@ -0,0 +1,96 @@
+/*
+ * 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 d653233..885d8e7 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,6 +413,10 @@
     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);
   }
@@ -733,6 +737,10 @@
     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 b953a47..628841c 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,6 +44,7 @@
 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;
@@ -224,6 +225,16 @@
   }
 
   @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 9add414..647b8f4 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,6 +34,7 @@
 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.jjs.ast.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;
@@ -41,7 +42,6 @@
 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.entryMethods.size() == 1) {
+    if (jprogram.getRunAsyncs().size() == 0) {
       // Don't do anything if there is no call to runAsync
       return;
     }
@@ -266,9 +266,7 @@
       throws UnableToCompleteException {
     Event codeSplitterEvent =
         SpeedTracerLogger.start(CompilerEventType.CODE_SPLITTER, "phase", "findSplitPoint");
-    Map<JMethod, List<Integer>> methodToSplitPoint =
-        reverseByEnclosingMethod(program.getRunAsyncReplacements());
-    Map<String, List<Integer>> nameToSplitPoint = reverseByName(program.getRunAsyncReplacements());
+    Map<String, List<Integer>> nameToSplitPoint = reverseByName(program.getRunAsyncs());
 
     if (refString.startsWith("@")) {
       JsniRef jsniRef = JsniRef.parse(refString);
@@ -297,7 +295,8 @@
       }
 
       JMethod method = (JMethod) referent;
-      List<Integer> splitPoints = methodToSplitPoint.get(method);
+      String canonicalName = ReplaceRunAsyncs.getImplicitName(method);
+      List<Integer> splitPoints = nameToSplitPoint.get(canonicalName);
       if (splitPoints == null) {
         branch.log(TreeLogger.ERROR, "Method does not enclose a runAsync call: " + jsniRef);
         throw new UnableToCompleteException();
@@ -451,7 +450,7 @@
 
     ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram);
     cfa.setDependencyRecorder(dependencyRecorder);
-    traverseEntry(jprogram, cfa, 0);
+    cfa.traverseEntryMethods();
     traverseClassArray(jprogram, cfa);
 
     dependencyRecorder.endDependencyGraph();
@@ -538,29 +537,9 @@
     logger.log(TreeLogger.TRACE, message.toString());
   }
 
-  /**
-   * 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) {
+  private static Map<String, List<Integer>> reverseByName(List<JRunAsync> runAsyncs) {
     Map<String, List<Integer>> revmap = new HashMap<String, List<Integer>>();
-    for (RunAsyncReplacement replacement : runAsyncReplacements.values()) {
+    for (JRunAsync replacement : runAsyncs) {
       String name = replacement.getName();
       if (name != null) {
         List<Integer> list = revmap.get(name);
@@ -568,7 +547,7 @@
           list = new ArrayList<Integer>();
           revmap.put(name, list);
         }
-        list.add(replacement.getNumber());
+        list.add(replacement.getSplitPoint());
       }
     }
     return revmap;
@@ -595,16 +574,6 @@
     }
   }
 
-  /**
-   * 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);
@@ -660,7 +629,7 @@
     this.dependencyRecorder = dependencyRecorder;
     this.initialLoadSequence = new LinkedHashSet<Integer>(jprogram.getSplitPointInitialSequence());
 
-    numEntries = jprogram.entryMethods.size();
+    numEntries = jprogram.getRunAsyncs().size() + 1;
     logging = Boolean.getBoolean(PROP_LOG_FRAGMENT_MAP);
     fieldToLiteralOfClass = buildFieldToClassLiteralMap(jprogram);
     fragmentExtractor = new FragmentExtractor(jprogram, jsprogram, map);
@@ -704,21 +673,26 @@
   private List<ControlFlowAnalyzer> computeAllButOneCfas() {
     String dependencyGraphNameAfterInitialSequence = dependencyGraphNameAfterInitialSequence();
 
-    List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>(numEntries - 1);
-
-    for (int entry = 1; entry < numEntries; entry++) {
-      if (isInitial(entry)) {
+    List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>();
+    for (JRunAsync runAsync : jprogram.getRunAsyncs()) {
+      int splitPoint = runAsync.getSplitPoint();
+      if (isInitial(splitPoint)) {
         allButOnes.add(null);
         continue;
       }
-      dependencyRecorder
-          .startDependencyGraph("sp" + entry, dependencyGraphNameAfterInitialSequence);
+      dependencyRecorder.startDependencyGraph("sp" + splitPoint,
+          dependencyGraphNameAfterInitialSequence);
       ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(liveAfterInitialSequence);
       cfa.setDependencyRecorder(dependencyRecorder);
-      traverseAllButEntry(cfa, entry);
-      // Traverse leftoversFragmentHasLoaded, because it should not
-      // go into any of the exclusive fragments.
-      cfa.traverseFromLeftoversFragmentHasLoaded();
+      for (JRunAsync otherRunAsync : jprogram.getRunAsyncs()) {
+        if (isInitial(otherRunAsync.getSplitPoint())) {
+          continue;
+        }
+        if (otherRunAsync == runAsync) {
+          continue;
+        }
+        cfa.traverseFromRunAsync(otherRunAsync);
+      }
       dependencyRecorder.endDependencyGraph();
       allButOnes.add(cfa);
     }
@@ -733,10 +707,8 @@
     dependencyRecorder.startDependencyGraph("total", null);
     ControlFlowAnalyzer everything = new ControlFlowAnalyzer(jprogram);
     everything.setDependencyRecorder(dependencyRecorder);
-    for (int entry = 0; entry < numEntries; entry++) {
-      traverseEntry(everything, entry);
-    }
-    everything.traverseFromLeftoversFragmentHasLoaded();
+    everything.traverseEntryMethods();
+    everything.traverseFromRunAsyncs();
     dependencyRecorder.endDependencyGraph();
     return everything;
   }
@@ -794,12 +766,14 @@
       extendsCfa = depGraphName;
 
       ControlFlowAnalyzer liveAfterSp = new ControlFlowAnalyzer(liveAfterInitialSequence);
-      traverseEntry(liveAfterSp, sp);
+      JRunAsync runAsync = jprogram.getRunAsyncs().get(sp - 1);
+      assert runAsync.getSplitPoint() == sp;
+      liveAfterSp.traverseFromRunAsync(runAsync);
       dependencyRecorder.endDependencyGraph();
 
       LivenessPredicate liveNow = new CfaLivenessPredicate(liveAfterSp);
 
-      List<JsStatement> statsToAppend = fragmentExtractor.createCallsToEntryMethods(sp);
+      List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(sp);
 
       addFragment(sp, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
 
@@ -812,13 +786,14 @@
      * Compute the exclusively live fragments. Each includes everything
      * exclusively live after entry point i.
      */
-    for (int i = 1; i < numEntries; i++) {
+    for (JRunAsync runAsync : jprogram.getRunAsyncs()) {
+      int i = runAsync.getSplitPoint();
       if (isInitial(i)) {
         continue;
       }
       LivenessPredicate alreadyLoaded = new ExclusivityMapLivenessPredicate(fragmentMap, 0);
       LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, i);
-      List<JsStatement> statsToAppend = fragmentExtractor.createCallsToEntryMethods(i);
+      List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(i);
       addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
     }
 
@@ -828,7 +803,7 @@
     {
       LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(liveAfterInitialSequence);
       LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, 0);
-      List<JsStatement> statsToAppend = fragmentExtractor.createCallToLeftoversFragmentHasLoaded();
+      List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(numEntries);
       addFragment(numEntries, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
     }
 
@@ -994,17 +969,19 @@
     }
     allFields.addAll(everything.getFieldsWritten());
 
-    for (int entry = 1; entry < numEntries; entry++) {
-      if (isInitial(entry)) {
+    for (JRunAsync runAsync : jprogram.getRunAsyncs()) {
+      int splitPoint = runAsync.getSplitPoint();
+      if (isInitial(splitPoint)) {
         continue;
       }
-      ControlFlowAnalyzer allButOne = allButOnes.get(entry - 1);
+      ControlFlowAnalyzer allButOne = allButOnes.get(splitPoint - 1);
       Set<JNode> allLiveNodes =
           union(allButOne.getLiveFieldsAndMethods(), allButOne.getFieldsWritten());
-      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()),
+      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()),
           declaredTypesIn(everything.getInstantiatedTypes()));
     }
   }
@@ -1023,21 +1000,4 @@
     (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 591b3ae..38568a2 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,6 +42,7 @@
 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.jjs.ast.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;
@@ -89,6 +90,7 @@
    */
   private class RescueVisitor extends JVisitor {
     private final ArrayList<JMethod> curMethodStack = new ArrayList<JMethod>();
+    private JMethod currentMethod;
 
     @Override
     public boolean visit(JArrayType type, Context ctx) {
@@ -366,6 +368,21 @@
     @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);
@@ -548,7 +565,10 @@
             curMethodStack.add(method);
             dependencyRecorder.methodIsLiveBecause(method, curMethodStack);
           }
+          JMethod lastMethod = currentMethod;
+          currentMethod = method;
           accept(method);
+          currentMethod = lastMethod;
           if (dependencyRecorder != null) {
             curMethodStack.remove(curMethodStack.size() - 1);
           }
@@ -791,9 +811,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>();
@@ -814,13 +834,15 @@
   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);
@@ -839,6 +861,8 @@
 
   public ControlFlowAnalyzer(JProgram program) {
     this.program = program;
+    asyncFragmentOnLoad = program.getIndexedMethod("AsyncFragmentLoader.onLoad");
+    runAsyncOnsuccess = program.getIndexedMethod("RunAsyncCallback.onSuccess");
     baseArrayType = program.getIndexedType("Array");
     buildMethodsOverriding();
   }
@@ -893,10 +917,26 @@
   }
 
   /**
-   * Traverse all code executed by <code>expr</code>.
+   * Traverse the program entry points, but don't traverse any runAsync
+   * fragments.
    */
-  public void traverseFrom(JExpression expr) {
-    rescuer.accept(expr);
+  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);
+    }
   }
 
   /**
@@ -914,17 +954,26 @@
     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 72cbd99..a522b6e 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,6 +34,7 @@
 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;
@@ -190,8 +191,6 @@
     return map.vtableInitToMethod(stat);
   }
 
-  private Set<JsName> entryMethodNames;
-
   private final JProgram jprogram;
 
   private final JsProgram jsprogram;
@@ -208,38 +207,18 @@
     this.jprogram = jprogram;
     this.jsprogram = jsprogram;
     this.map = map;
-
-    buildEntryMethodSet();
   }
 
   /**
-   * Add direct calls to the entry methods of the specified entry number.
+   * Create a call to {@link AsyncFragmentLoader#onLoad}.
    */
-  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);
+  public List<JsStatement> createOnLoadedCall(int splitPoint) {
+    JMethod loadMethod = jprogram.getIndexedMethod("AsyncFragmentLoader.onLoad");
+    JsName loadMethodName = map.nameForMethod(loadMethod);
     SourceInfo sourceInfo = jsprogram.getSourceInfo();
     JsInvocation call = new JsInvocation(sourceInfo);
-    call.setQualifier(wrapWithEntry(loadedMethodName.makeRef(sourceInfo)));
+    call.setQualifier(wrapWithEntry(loadMethodName.makeRef(sourceInfo)));
+    call.getArguments().add(new JsNumberLiteral(sourceInfo, splitPoint));
     List<JsStatement> newStats = Collections.<JsStatement> singletonList(call.makeStmt());
     return newStats;
   }
@@ -260,45 +239,41 @@
      */
     JClassType currentVtableType = null;
 
-    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);
+    // 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;
         } else {
-          keepIt = isLive(stat, livenessPredicate) && !isLive(stat, alreadyLoadedPredicate);
+          keepIt = false;
         }
+      } 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;
-          }
-          JClassType vtableType = vtableTypeNeeded(stat);
-          if (vtableType != null && vtableType != currentVtableType) {
-            extractedStats.add(vtableStatFor(vtableType));
-            currentVtableType = vtableType;
-          }
-          extractedStats.add(stat);
+      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);
       }
     }
 
@@ -327,23 +302,6 @@
     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
@@ -416,27 +374,6 @@
     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
deleted file mode 100644
index 6bfa2de..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * 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 ff3e1f7..1f552ec 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#getAllEntryMethods()}).
+     * program ({@link JProgram#getEntryMethods()}).
      */
     private JsFunction[] entryFunctions;
 
     /**
      * A reverse index for the entry methods of the program (
-     * {@link JProgram#getAllEntryMethods()}). Each entry method is mapped to
-     * its integer index.
+     * {@link JProgram#getEntryMethods()}). 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(Arrays.asList(entryFunctions).subList(0, x.getEntryCount(0)), globalStmts);
+      generateGwtOnLoad(Lists.create(entryFunctions), globalStmts);
 
       // Add a few things onto the beginning.
 
@@ -1238,28 +1238,13 @@
         }
       }
 
-      /*
-       * 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());
-        }
+      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);
       }
     }
 
@@ -1422,7 +1407,7 @@
        * Arrange for entryFunctions to be filled in as functions are visited.
        * See their Javadoc comments for more details.
        */
-      List<JMethod> entryMethods = x.getAllEntryMethods();
+      List<JMethod> entryMethods = x.getEntryMethods();
       entryFunctions = new JsFunction[entryMethods.size()];
       entryMethodToIndex = new IdentityHashMap<JMethod, Integer>();
       for (int i = 0; i < entryMethods.size(); i++) {
@@ -1703,6 +1688,7 @@
       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);
@@ -2050,7 +2036,7 @@
     @Override
     public void endVisit(JProgram x, Context ctx) {
       // Entry methods can be called externally, so they must run clinit.
-      crossClassTargets.addAll(x.getAllEntryMethods());
+      crossClassTargets.addAll(x.getEntryMethods());
     }
 
     @Override
@@ -2091,9 +2077,7 @@
 
     @Override
     public void endVisit(JProgram x, Context ctx) {
-      for (List<JMethod> methods : x.entryMethods) {
-        Collections.sort(methods, hasNameSort);
-      }
+      Collections.sort(x.getEntryMethods(), 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 6bef7d4..825b433 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,6 +81,11 @@
       }
     }
 
+    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 aef2ca7..0c2cfd3 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,17 +304,6 @@
          */
         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));
     }
 
@@ -366,18 +355,6 @@
     }
   }
 
-  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 199ab83..5f3f141 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,13 +99,6 @@
     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 5b37451..9a74db2 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.getAllEntryMethods()) {
+      for (JMethod method : program.getEntryMethods()) {
         accept(method);
       }
       for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext();) {
@@ -607,10 +607,8 @@
        */
       traverseFromCodeGenTypes(livenessAnalyzer);
     }
-    for (JMethod method : program.getAllEntryMethods()) {
-      livenessAnalyzer.traverseFrom(method);
-    }
-    livenessAnalyzer.traverseFromLeftoversFragmentHasLoaded();
+    livenessAnalyzer.traverseEntryMethods();
+    livenessAnalyzer.traverseFromRunAsyncs();
 
     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 d0ae938..2323b87 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,17 +24,18 @@
 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.JType;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.jjs.ast.JRunAsync;
 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;
@@ -50,63 +51,8 @@
  * 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) {
@@ -116,11 +62,17 @@
         String name;
         switch (x.getArgs().size()) {
           case 1:
-            name = null;
+            name = getImplicitName(currentMethod);
             asyncCallback = x.getArgs().get(0);
             break;
           case 2:
-            name = nameFromClassLiteral((JClassLiteral) x.getArgs().get(0));
+            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);
             asyncCallback = x.getArgs().get(1);
             break;
           default:
@@ -128,21 +80,30 @@
                 "runAsync call found with neither 1 nor 2 arguments: " + x);
         }
 
-        int entryNumber = entryCount++;
-        JClassType loader = getFragmentLoader(entryNumber);
-        JMethod loadMethod = getRunAsyncMethod(loader);
-        assert loadMethod != null;
-        runAsyncReplacements.put(entryNumber, new RunAsyncReplacement(entryNumber, currentMethod,
-            loadMethod, name));
+        int splitPoint = runAsyncs.size() + 1;
+        SourceInfo info = x.getSourceInfo();
 
-        JMethodCall methodCall = new JMethodCall(x.getSourceInfo(), null, loadMethod);
-        methodCall.addArg(asyncCallback);
+        JMethod runAsyncMethod = program.getIndexedMethod("AsyncFragmentLoader.runAsync");
+        assert runAsyncMethod != null;
+        JMethodCall runAsyncCall = new JMethodCall(info, null, runAsyncMethod);
+        runAsyncCall.addArg(JIntLiteral.get(splitPoint));
+        runAsyncCall.addArg(asyncCallback);
 
-        tightenCallbackType(entryNumber, asyncCallback.getType());
+        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);
 
-        program.addEntryMethod(getOnLoadMethod(loader), entryNumber);
-
-        ctx.replaceMe(methodCall);
+        JRunAsync runAsyncNode = new JRunAsync(info, splitPoint, name, runAsyncCall, onSuccessCall);
+        runAsyncs.add(runAsyncNode);
+        ctx.replaceMe(runAsyncNode);
       }
     }
 
@@ -159,62 +120,20 @@
       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<RunAsyncReplacement>> replacementsByName;
+    private final Map<String, List<JRunAsync>> replacementsByName;
+    private final JMethod runAsyncCode;
 
     public ReplaceRunAsyncResources() {
-      replacementsByName = new HashMap<String, List<RunAsyncReplacement>>();
-      for (RunAsyncReplacement replacement : runAsyncReplacements.values()) {
+      replacementsByName = new HashMap<String, List<JRunAsync>>();
+      runAsyncCode = program.getIndexedMethod("RunAsyncCode.runAsyncCode");
+      for (JRunAsync replacement : runAsyncs) {
         String name = replacement.getName();
         if (name != null) {
-          List<RunAsyncReplacement> list = replacementsByName.get(name);
+          List<JRunAsync> list = replacementsByName.get(name);
           if (list == null) {
-            list = new ArrayList<RunAsyncReplacement>();
+            list = new ArrayList<JRunAsync>();
             replacementsByName.put(name, list);
           }
           list.add(replacement);
@@ -224,7 +143,7 @@
 
     @Override
     public void endVisit(JMethodCall x, Context ctx) {
-      if (x.getTarget() == program.getIndexedMethod("RunAsyncCode.runAsyncCode")) {
+      if (x.getTarget() == runAsyncCode) {
         JExpression arg0 = x.getArgs().get(0);
         if (!(arg0 instanceof JClassLiteral)) {
           error(arg0.getSourceInfo(), "Only a class literal may be passed to runAsyncCode");
@@ -232,42 +151,28 @@
         }
         JClassLiteral lit = (JClassLiteral) arg0;
         String name = nameFromClassLiteral(lit);
-        List<RunAsyncReplacement> matches = replacementsByName.get(name);
+        List<JRunAsync> matches = replacementsByName.get(name);
+        SourceInfo info = x.getSourceInfo();
         if (matches == null || matches.size() == 0) {
-          error(x.getSourceInfo(), "No runAsync call is named " + name);
+          error(info, "No runAsync call is named " + name);
           return;
         }
         if (matches.size() > 1) {
-          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()));
+          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() + "'");
           }
           return;
         }
-        Integer splitPoint = matches.get(0).getNumber();
-
+        int splitPoint = matches.get(0).getSplitPoint();
         JMethodCall newCall =
-            new JMethodCall(x.getSourceInfo(), null, program
+            new JMethodCall(info, 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 {
@@ -290,33 +195,15 @@
     return initializerCall;
   }
 
-  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;
+  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;
   }
 
   /**
@@ -330,8 +217,7 @@
   private final TreeLogger logger;
   private final JProgram program;
 
-  private final Map<Integer, RunAsyncReplacement> runAsyncReplacements =
-      new HashMap<Integer, RunAsyncReplacement>();
+  private final List<JRunAsync> runAsyncs = new ArrayList<JRunAsync>();
 
   private ReplaceRunAsyncs(TreeLogger logger, JProgram program) {
     this.logger = logger;
@@ -341,7 +227,7 @@
   private TreeLogger error(SourceInfo info, String message) {
     errorsFound = true;
     TreeLogger fileLogger =
-        logger.branch(TreeLogger.ERROR, "Error in '" + info.getFileName() + "'");
+        logger.branch(TreeLogger.ERROR, "Errors in '" + info.getFileName() + "'");
     String linePrefix = "";
     if (info.getStartLine() > 0) {
       linePrefix = "Line " + info.getStartLine() + ": ";
@@ -353,43 +239,14 @@
   private void execImpl() throws UnableToCompleteException {
     AsyncCreateVisitor visitor = new AsyncCreateVisitor();
     visitor.accept(program);
-    setNumEntriesInAsyncFragmentLoader(visitor.entryCount);
-    program.setRunAsyncReplacements(runAsyncReplacements);
+    setNumEntriesInAsyncFragmentLoader(runAsyncs.size() + 1);
+    program.setRunAsyncs(runAsyncs);
     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 016e0bf..63ff5c6 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,11 +674,7 @@
 
       if (refType instanceof JArrayType) {
         JArrayType arrayType = (JArrayType) refType;
-        JArrayType newArrayType = nullifyArrayType(arrayType);
-        if (arrayType != newArrayType) {
-          x.setType(newArrayType);
-          madeChanges();
-        }
+        refType = nullifyArrayType(arrayType);
       }
 
       // tighten based on leaf types
@@ -739,7 +735,7 @@
         }
       }
 
-      if (refType != resultType) {
+      if (x.getType() != 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 042fdbf5..1083bba 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java
@@ -25,42 +25,17 @@
 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.HashMap;
-import java.util.Map;
+import java.util.Set;
 
 /**
  * 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
@@ -72,22 +47,29 @@
       JsFunction f = (JsFunction) x.getExpression();
       JsName name = f.getName();
 
-      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();
+      // Anonymous function, ignore it
+      if (name == null || seen.contains(name) || f.isArtificiallyRescued()) {
+        return;
       }
+
+      // 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());
     }
   }
 
@@ -102,7 +84,7 @@
   }
 
   private final JsProgram program;
-  private final Map<JsName, JsFunction> toRemove = new HashMap<JsName, JsFunction>();
+  private final Set<JsName> seen = new IdentityHashSet<JsName>();
 
   public JsUnusedFunctionRemover(JsProgram program) {
     this.program = program;
@@ -111,11 +93,8 @@
   public OptimizerStats execImpl() {
     OptimizerStats stats = new OptimizerStats(NAME);
 
-    // Find all functions
-    (new JsFunctionVisitor()).accept(program);
-
-    // Remove the functions that are referenced from the hit list
-    (new JsNameRefVisitor()).accept(program);
+    // Rescue all referenced functions.
+    new RescueVisitor().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 98c3c23..85487b2 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -51,6 +51,26 @@
  */
 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() {
@@ -153,7 +173,23 @@
     public CharSequence getContent() {
       StringBuffer code = new StringBuffer();
       code.append("package com.google.gwt.core.client;\n");
-      code.append("public interface RunAsyncCallback { }\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("}");
       return code;
     }
   };
@@ -255,7 +291,8 @@
     // Replace the basic Class and Enum with a compiler-specific one.
     result.remove(JavaResourceBase.CLASS);
     result.remove(JavaResourceBase.ENUM);
-    Collections.addAll(result, ARRAY, CAST, CLASS, CLASSLITERALHOLDER, ENUM, GWT, RUNASYNCCALLBACK);
+    Collections.addAll(result, ASYNCFRAGMENTLOADER, ARRAY, CAST, CLASS, CLASSLITERALHOLDER, ENUM,
+        GWT, RUNASYNCCALLBACK, RUNASYNCCODE);
     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 d91d4d2..f8f9808 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,101 +50,25 @@
         return code;
       }
     });
+
     addSnippetImport("test.SplitPoint3");
-
-    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)");
-
+    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'");
     testSnippet("RunAsyncCode.runAsyncCode(SplitPoint1.class);");
   }
 
   public void testNonClassLiteral() {
-    expectError("Line 14: Only a class literal may be passed to runAsyncCode");
+    expectError("Line 7: Only a class literal may be passed to runAsyncCode");
     testSnippet("RunAsyncCode.runAsyncCode(new SplitPoint1().getClass());");
   }
 
   public void testNonExistentSplitPoint() {
-    expectError("Line 14: No runAsync call is named java.lang.String");
+    expectError("Line 7: 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") {
@@ -187,7 +111,7 @@
   private void initializeTestLoggerBuilder() {
     testLoggerBuilder = new UnitTestTreeLogger.Builder();
     testLoggerBuilder.setLowestLogLevel(TreeLogger.ERROR);
-    expectError("Error in '/mock/test/EntryPoint.java'");
+    expectError("Errors 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 e35dd65..77de550 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,6 +18,7 @@
 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;
 
 /**
@@ -61,14 +62,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 {
-      compileSnippet("void", "GWT.runAsync((new Object()).getClass(), null);");
+      JProgram program = compileSnippet("void", "GWT.runAsync((new Object()).getClass(), null);");
+      ReplaceRunAsyncs.exec(logger, program);
       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 88547b8..3e00070 100644
--- a/tools/api-checker/config/gwt23_24userApi.conf
+++ b/tools/api-checker/config/gwt23_24userApi.conf
@@ -116,7 +116,8 @@
 
 ##############################################
 #excluded packages colon separated list
-excludedPackages com.google.gwt.editor.client.impl\
+excludedPackages com.google.gwt.core.client.impl\
+: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 8861641..4e66b6c 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.core.client;
 
-import com.google.gwt.core.client.impl.AsyncFragmentLoader;
 import com.google.gwt.core.client.impl.Impl;
 
 /**
@@ -246,14 +245,14 @@
    */
   @SuppressWarnings("unused") // parameter will be used following replacement
   public static void runAsync(Class<?> name, RunAsyncCallback callback) {
-    runAsyncWithoutCodeSplitting(callback);
+    callback.onSuccess();
   }
 
   /**
    * Run the specified callback once the necessary code for it has been loaded.
    */
   public static void runAsync(RunAsyncCallback callback) {
-    runAsyncWithoutCodeSplitting(callback);
+    callback.onSuccess();
   }
 
   /**
@@ -283,36 +282,4 @@
   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 c95c5ea..a86590c 100644
--- a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.RunAsyncCallback;
 
 /**
  * <p>
@@ -265,13 +266,22 @@
   public static AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{});
 
   /**
-   * 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.
+   * Called by compiler-generated code when a fragment is loaded.
+   * 
+   * @param fragment the fragment number
    */
-  public static void browserLoaderLeftoversFragmentHasLoaded() {
-    BROWSER_LOADER.leftoversFragmentHasLoaded();
+  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);
   }
 
   /**
@@ -291,6 +301,11 @@
   }
 
   /**
+   * Callbacks indexed by fragment number.
+   */
+  private final RunAsyncCallback[][] allCallbacks;
+
+  /**
    * The fragment currently loading, or -1 if there aren't any.
    */
   private int fragmentLoading = -1;
@@ -358,15 +373,46 @@
     this.loadingStrategy = loadingStrategy;
     this.logger = logger;
     int numEntriesPlusOne = numEntries + 1;
-    requestedExclusives = new BoundedIntQueue(numEntriesPlusOne);
-    isLoaded = new boolean[numEntriesPlusOne];
-    pendingDownloadErrorHandlers = new LoadTerminatedHandler[numEntriesPlusOne];
+    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;
   }
 
   /**
    * Inform the loader that a fragment has now finished loading.
    */
-  public void fragmentHasLoaded(int fragment) {
+  void fragmentHasLoaded(int fragment) {
     logFragmentLoaded(fragment);
     if (fragment < pendingDownloadErrorHandlers.length) {
       pendingDownloadErrorHandlers[fragment] = null;
@@ -395,7 +441,7 @@
    * 
    * @param splitPoint the split point whose code needs to be loaded
    */
-  public void inject(int splitPoint, LoadTerminatedHandler loadErrorHandler) {
+  void inject(int splitPoint, LoadTerminatedHandler loadErrorHandler) {
     pendingDownloadErrorHandlers[splitPoint] = loadErrorHandler;
     if (!isInitial(splitPoint)) {
       requestedExclusives.add(splitPoint);
@@ -403,49 +449,8 @@
     startLoadingNextFragment();
   }
 
-  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;
+  void leftoversFragmentHasLoaded() {
+    onLoadImpl(leftoversFragment());
   }
 
   private boolean anyPrefetchesRequested() {
@@ -522,6 +527,10 @@
     return false;
   }
 
+  private boolean isLoading(int splitPoint) {
+    return pendingDownloadErrorHandlers[splitPoint] != null;
+  }
+
   private int leftoversFragment() {
     return numEntries;
   }
@@ -531,6 +540,13 @@
   }
 
   /**
+   * 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>.
@@ -544,6 +560,58 @@
     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 7d9c4be..5fa8101 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 44a5adf..d47185f 100644
--- a/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
@@ -116,11 +116,10 @@
     // save the original handler
     final UncaughtExceptionHandler originalHandler = GWT.getUncaughtExceptionHandler();
 
-    // set a handler that catches toThrow and nothing else
-    GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
+    // set a handler that looks for toThrow
+    GWT.UncaughtExceptionHandler myHandler = new GWT.UncaughtExceptionHandler() {
       public void onUncaughtException(Throwable e) {
         GWT.setUncaughtExceptionHandler(originalHandler);
-
         if (e == toThrow) {
           // expected
           finishTest();
@@ -129,17 +128,22 @@
           throw new RuntimeException(e);
         }
       }
-    });
-
+    };
+    GWT.setUncaughtExceptionHandler(myHandler);
     delayTestFinish(RUNASYNC_TIMEOUT);
 
-    GWT.runAsync(new RunAsyncCallback() {
-      public void onFailure(Throwable caught) {
-      }
+    try {
+      GWT.runAsync(new RunAsyncCallback() {
+        public void onFailure(Throwable caught) {
+        }
 
-      public void onSuccess() {
-        throw toThrow;
-      }
-    });
+        public void onSuccess() {
+          throw toThrow;
+        }
+      });
+    } catch (Throwable e) {
+      // runAsync can either throw immediately, or throw uncaught.
+      myHandler.onUncaughtException(e);
+    }
   }
 }