Rollback of r10216 "Re-implement runAsync to improve code size", caused some subtle errors.

Review by: zundel@google.com

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