Working CodeSplitter2.
-With this the people should be able to test the new CodeSplitter.
-It is protected by a -XfragmentMerge flag, it will not break anyone.
-While it is working in lots of internal projects, there are still room for improvement. There are enough todo's in places where work is needed.

Review at http://gwt-code-reviews.appspot.com/1631803


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10866 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 4d033ed..d00aa4e 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -503,7 +503,7 @@
 
   /**
    * Logs the total script size for this permutation, as calculated by
-   * {@link CodeSplitter#totalScriptSize(int[])}.
+   * {@link CodeSplitter2#totalScriptSize(int[])}.
    */
   private static void logScriptSize(TreeLogger logger, int permId,
       StandardCompilationResult compilation) {
@@ -518,6 +518,7 @@
       jsLengths[i] = javaScript[i].length();
     }
 
+    // TODO(acleung): This is broken for CodeSplitter2.
     int totalSize = CodeSplitter.totalScriptSize(jsLengths);
 
     if (logger.isLoggable(TreeLogger.TRACE)) {
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java
index 5f13163..770a6f5 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java
@@ -29,6 +29,7 @@
 import com.google.gwt.dev.util.arg.ArgHandlerDumpSignatures;
 import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions;
 import com.google.gwt.dev.util.arg.ArgHandlerEnableClosureCompiler;
+import com.google.gwt.dev.util.arg.ArgHandlerFragmentMerge;
 import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
 import com.google.gwt.dev.util.arg.ArgHandlerMaxPermsPerPrecompile;
 import com.google.gwt.dev.util.arg.ArgHandlerOptimize;
@@ -62,6 +63,7 @@
     registerHandler(new ArgHandlerCompilerMetrics(options));
     registerHandler(new ArgHandlerDisableSoycHtml(options));
     registerHandler(new ArgHandlerEnableClosureCompiler(options));
+    registerHandler(new ArgHandlerFragmentMerge(options));
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
index 1fea930..1a3ab05 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
@@ -49,6 +49,11 @@
   }
 
   @Override
+  public int getFragmentsMerge() {
+    return jjsOptions.getFragmentsMerge();
+  }
+
+  @Override
   public File getGenDir() {
     return genDir;
   }
@@ -188,6 +193,11 @@
   }
 
   @Override
+  public void setFragmentsMerge(int numFragments) {
+    jjsOptions.setFragmentsMerge(numFragments);
+  }
+
+  @Override
   public void setGenDir(File genDir) {
     this.genDir = genDir;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
index 3162e5e..18fa84a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.util.arg.OptionDisableClassMetadata;
 import com.google.gwt.dev.util.arg.OptionEnableAssertions;
 import com.google.gwt.dev.util.arg.OptionEnableClosureCompiler;
+import com.google.gwt.dev.util.arg.OptionFragmentsMerge;
 import com.google.gwt.dev.util.arg.OptionOptimize;
 import com.google.gwt.dev.util.arg.OptionOptimizePrecompile;
 import com.google.gwt.dev.util.arg.OptionRunAsyncEnabled;
@@ -36,6 +37,6 @@
     OptionDisableClassMetadata, OptionDisableCastChecking, OptionEnableAssertions,
     OptionRunAsyncEnabled, OptionScriptStyle, OptionSoycEnabled, OptionSoycDetailed,
     OptionOptimizePrecompile, OptionStrict, OptionSoycHtmlDisabled,
-    OptionEnableClosureCompiler {
+    OptionEnableClosureCompiler, OptionFragmentsMerge {
 
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
index c1695bb..173df6f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -38,6 +38,7 @@
   private boolean soycHtmlDisabled = false;
   private boolean strict = false;
   private boolean closureCompilerEnabled;
+  private int fragmentsMerge = -1;
 
   public JJSOptionsImpl() {
   }
@@ -60,8 +61,14 @@
     setSoycHtmlDisabled(other.isSoycHtmlDisabled());
     setStrict(other.isStrict());
     setClosureCompilerEnabled(other.isClosureCompilerEnabled());
+    setFragmentsMerge(other.getFragmentsMerge());
   }
-
+  
+  @Override
+  public int getFragmentsMerge() {
+    return fragmentsMerge;
+  }
+  
   @Override
   public int getOptimizationLevel() {
     return optimizationLevel;
@@ -167,6 +174,11 @@
   }
 
   @Override
+  public void setFragmentsMerge(int numFragments) {
+    this.fragmentsMerge = numFragments;
+  }
+
+  @Override
   public void setOptimizationLevel(int level) {
     optimizationLevel = level;
   }
@@ -205,4 +217,5 @@
   public void setStrict(boolean strict) {
     this.strict = strict;
   }
+
 }
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 9a028a8..5f3c93d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -67,8 +67,9 @@
 import com.google.gwt.dev.jjs.impl.AstDumper;
 import com.google.gwt.dev.jjs.impl.CastNormalizer;
 import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
-import com.google.gwt.dev.jjs.impl.CodeSplitter;
 import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
+import com.google.gwt.dev.jjs.impl.CodeSplitter;
+import com.google.gwt.dev.jjs.impl.CodeSplitter2;
 import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
 import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
 import com.google.gwt.dev.jjs.impl.EnumOrdinalizer;
@@ -348,10 +349,21 @@
 
       // (10) Split up the program into fragments
       SyntheticArtifact dependencies = null;
+    
       if (options.isRunAsyncEnabled()) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, chooseDependencyRecorder(options
-            .isSoycEnabled(), baos));
+        int fragmentsMerge = options.getFragmentsMerge();
+        
+        // Pick and choose which code splitter to use. Only use the experimental
+        // one when the user explicitly decides the project needs fragment
+        // merging.
+        if (fragmentsMerge > 0) {
+          CodeSplitter2.exec(logger, jprogram, jsProgram, jjsmap, fragmentsMerge,
+              chooseDependencyRecorder(options.isSoycEnabled(), baos));
+        } else {
+          CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, chooseDependencyRecorder(options
+              .isSoycEnabled(), baos));
+        }
         if (baos.size() == 0 && options.isSoycEnabled()) {
           recordNonSplitDependencies(jprogram, baos);
         }
@@ -618,7 +630,7 @@
       // Fix up GWT.runAsync()
       if (module != null && options.isRunAsyncEnabled()) {
         ReplaceRunAsyncs.exec(logger, jprogram);
-        CodeSplitter.pickInitialLoadSequence(logger, jprogram, module.getProperties());
+        CodeSplitter2.pickInitialLoadSequence(logger, jprogram, module.getProperties());
       }
 
       ImplementClassLiteralsAsFields.exec(jprogram);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNumericEntry.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNumericEntry.java
new file mode 100644
index 0000000..77e8c57
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNumericEntry.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+/**
+ * A place order a numeric value in the AST.
+ * 
+ * It temporary holds an numeric value during compilation. In a later stage,
+ * a compiler pass easily replace these place holder. For example,
+ * {@link ReplaceRunAsyncs would }
+ */
+public final class JNumericEntry extends JExpression {
+
+  private final String key;
+  private int value;
+
+  public JNumericEntry(SourceInfo info, String key, int value) {
+    super(info);
+    this.key = key;
+    this.value = value;
+  }
+  
+  public String getKey() {
+    return key;
+  }
+
+  @Override
+  public JType getType() {
+    return JPrimitiveType.INT;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  @Override
+  public boolean hasSideEffects() {
+    return false;
+  }
+
+  public void setValue(int value) {
+    this.value = value;
+  }
+
+  @Override
+  public void traverse(JVisitor visitor, Context ctx) {
+    if (visitor.visit(this, ctx)) {
+    }
+    visitor.endVisit(this, ctx);
+  }
+  
+}
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 95aba96..18835af 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
@@ -21,6 +21,7 @@
 import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.jjs.ast.js.JsCastMap;
 import com.google.gwt.dev.jjs.impl.CodeSplitter;
+import com.google.gwt.dev.jjs.impl.CodeSplitter2.FragmentPartitioningResult;
 import com.google.gwt.dev.util.collect.Lists;
 
 import java.io.IOException;
@@ -45,6 +46,7 @@
  * Root for the AST representing an entire Java program.
  */
 public class JProgram extends JNode {
+
   private static final class ArrayTypeComparator implements Comparator<JArrayType>, Serializable {
     public int compare(JArrayType o1, JArrayType o2) {
       int comp = o1.getDims() - o2.getDims();
@@ -244,18 +246,29 @@
    * be preferred whenever a JProgram instance is available.
    * 
    * @param initialSeq The initial split point sequence of the program
+   * @param result The fragment partitioning result, null if it hasn't be partitioned.
    * @param numSps The number of split points in the program
    * @param firstFragment The first fragment to consider
    * @param restFragments The rest of the fragments to consider
    */
-  public static int lastFragmentLoadingBefore(List<Integer> initialSeq, int numSps,
-      int firstFragment, int... restFragments) {
+  public static int lastFragmentLoadingBefore(List<Integer> initialSeq,
+      FragmentPartitioningResult result, int numSps, int firstFragment, int... restFragments) {
     int latest = firstFragment;
     for (int frag : restFragments) {
-      latest = pairwiseLastFragmentLoadingBefore(initialSeq, numSps, latest, frag);
+      latest = pairwiseLastFragmentLoadingBefore(initialSeq, result, numSps, latest, frag);
     }
     return latest;
   }
+  
+  public static int lastFragmentLoadingBefore(List<Integer> initialSeq,
+      int numSps, int firstFragment, int... restFragments) {
+    int latest = firstFragment;
+    for (int frag : restFragments) {
+      latest = pairwiseLastFragmentLoadingBefore(initialSeq, null, numSps, latest, frag);
+    }
+    return latest;
+  }
+
 
   public static void serializeTypes(List<JDeclaredType> types, ObjectOutputStream stream)
       throws IOException {
@@ -272,8 +285,9 @@
    * The main logic behind {@link #lastFragmentLoadingBefore(int, int...)} and
    * {@link #lastFragmentLoadingBefore(List, int, int, int...)}.
    */
-  private static int pairwiseLastFragmentLoadingBefore(List<Integer> initialSeq, int numSps,
-      int frag1, int frag2) {
+  private static int pairwiseLastFragmentLoadingBefore(List<Integer> initialSeq,
+      FragmentPartitioningResult result, int numSps, int frag1, int frag2) {
+
     if (frag1 == frag2) {
       return frag1;
     }
@@ -286,10 +300,22 @@
       return 0;
     }
 
-    // See if either is in the initial sequence
-    int initPos1 = initialSeq.indexOf(frag1);
-    int initPos2 = initialSeq.indexOf(frag2);
+    // TODO(acleung): While the logic for this is correct, the terminology used
+    // in the code is not correct when fragment partitioning is on.
+    // Once the new code splitter is the default. This function needs to be
+    // rewritten.
+    int sp1 = frag1;
+    int sp2 = frag2;
 
+    // If there were some fragment merging.
+    if (result != null) {
+      sp1 = result.getSplitPointFromFragmnet(sp1);
+      sp2 = result.getSplitPointFromFragmnet(sp2);
+    }
+    
+    int initPos1 = initialSeq.indexOf(sp1);
+    int initPos2 = initialSeq.indexOf(sp2);
+    
     // If both are in the initial sequence, then pick the earlier
     if (initPos1 >= 0 && initPos2 >= 0) {
       if (initPos1 < initPos2) {
@@ -309,8 +335,11 @@
     assert (initPos1 < 0 && initPos2 < 0);
     assert (frag1 != frag2);
 
-    // They are both leftovers or exclusive. Leftovers goes first in all cases.
-    return CodeSplitter.getLeftoversFragmentNumber(numSps);
+    if (result != null) {
+      return result.getLeftoverFragmentIndex();
+    } else {
+      return CodeSplitter.getLeftoversFragmentNumber(numSps);
+    }
   }
 
   public final List<JClassType> codeGenTypes = new ArrayList<JClassType>();
@@ -369,6 +398,8 @@
   private JClassType typeSpecialJavaScriptObject;
 
   private JClassType typeString;
+  
+  private FragmentPartitioningResult fragmentPartitioninResult;
 
   /**
    * Constructor.
@@ -897,8 +928,8 @@
    * supplied fragments, or it might be a common predecessor.
    */
   public int lastFragmentLoadingBefore(int firstFragment, int... restFragments) {
-    return lastFragmentLoadingBefore(splitPointInitialSequence, runAsyncs.size(), firstFragment,
-        restFragments);
+    return lastFragmentLoadingBefore(splitPointInitialSequence, fragmentPartitioninResult,
+        runAsyncs.size(), firstFragment, restFragments);
   }
 
   public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) {
@@ -932,6 +963,10 @@
     }
   }
 
+  public void setFragmentPartitioningResult(FragmentPartitioningResult result) {
+    fragmentPartitioninResult = result;
+  }
+
   public void setRunAsyncs(List<JRunAsync> runAsyncs) {
     this.runAsyncs = Lists.normalizeUnmodifiable(runAsyncs);
   }
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 8e17e97..3d332ff 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
@@ -376,6 +376,10 @@
   public void endVisit(JNullType x, Context ctx) {
     endVisit((JReferenceType) x, ctx);
   }
+  
+  public void endVisit(JNumericEntry x, Context ctx) {
+    endVisit((JExpression) x, ctx);
+  }
 
   public void endVisit(JParameter x, Context ctx) {
     endVisit((JVariable) x, ctx);
@@ -704,6 +708,10 @@
   public boolean visit(JNullType x, Context ctx) {
     return visit((JReferenceType) x, ctx);
   }
+  
+  public boolean visit(JNumericEntry x, Context ctx) {
+    return visit((JExpression) x, ctx);
+  }
 
   public boolean visit(JParameter x, Context ctx) {
     return visit((JVariable) 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..d1089da 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
@@ -41,6 +41,7 @@
 import com.google.gwt.dev.jjs.ast.JNewArray;
 import com.google.gwt.dev.jjs.ast.JNewInstance;
 import com.google.gwt.dev.jjs.ast.JNullLiteral;
+import com.google.gwt.dev.jjs.ast.JNumericEntry;
 import com.google.gwt.dev.jjs.ast.JParameterRef;
 import com.google.gwt.dev.jjs.ast.JPostfixOperation;
 import com.google.gwt.dev.jjs.ast.JPrefixOperation;
@@ -266,6 +267,12 @@
   }
 
   @Override
+  public boolean visit(JNumericEntry x, Context ctx) {
+    expression = new JNumericEntry(x.getSourceInfo(), x.getKey(), x.getValue());
+    return false;
+  }
+  
+  @Override
   public boolean visit(JNullLiteral x, Context ctx) {
     expression = x;
     return false;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java
index 4e317f3..9d9e82b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java
@@ -28,22 +28,25 @@
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
 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.JNewArray;
 import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JNumericEntry;
 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;
+import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
 import com.google.gwt.dev.jjs.impl.FragmentExtractor.CfaLivenessPredicate;
 import com.google.gwt.dev.jjs.impl.FragmentExtractor.LivenessPredicate;
 import com.google.gwt.dev.jjs.impl.FragmentExtractor.NothingAlivePredicate;
 import com.google.gwt.dev.js.ast.JsBlock;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsNumericEntry;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.util.JsniRef;
@@ -52,14 +55,12 @@
 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 com.google.gwt.thirdparty.guava.common.base.Predicate;
 
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -81,40 +82,62 @@
  * code.
  * 
  * TODO(acleung): Rename to CodeSplitter upon completion.
+ * TODO(acleung): Some of the data structures and methods are EXACT copy of the
+ *                original CoderSplitter.java. This is intentional as we are going to remove
+ *                the old one upon completion of this one.
  * TODO(acleung): Figure out how to integrate with SOYC and dependency tracker.
  * TODO(acleung): Insert SpeedTracer calls at performance sensitive places.
  * TODO(acleung): Insert logger calls to generate meaningful logs.
- * TODO(acleung): Modify the fragment loader.
- * TODO(acleung): May be add back the odd heuristics if needed.
+ * TODO(acleung): May be add back the old heuristics if needed.
  */
 public class CodeSplitter2 {
-  
+
   /**
-   * A dependency recorder that can record multiple dependency graphs. It has
-   * methods for starting and finishing new dependency graphs.
+   * A read-only class that holds some information about the result of the
+   * partition process.
    * 
-   * TODO(acleung): This is currently broken. I need to investiage more on how we inform SOYC
-   * the dependency results.
+   * Unlike the original code split where information about the fragments and
+   * be deduced from the JProgram, certain compiler passes needs to know what
+   * happened here in order to do their job correctly.
    */
-  public interface MultipleDependencyGraphRecorder extends DependencyRecorder {
+  public static final class FragmentPartitioningResult {
+    private final int[] fragmentToSplitPoint;
+    
+    private FragmentPartitioningResult(int[] splitPointToFragmentMap, int numFragments) {
+      fragmentToSplitPoint = new int[numFragments];
+      for (int i = 0, len = splitPointToFragmentMap.length - 1; i < len; i++) {
+        System.out.println("splitPointToFragmentMap[" + i + "] = " + splitPointToFragmentMap[i]);
+      }
+      for (int i = 1, len = splitPointToFragmentMap.length - 1; i < len; i++) {
+        if (fragmentToSplitPoint[splitPointToFragmentMap[i]] == 0) {
+          fragmentToSplitPoint[splitPointToFragmentMap[i]] = i;
+        } else {
+          fragmentToSplitPoint[splitPointToFragmentMap[i]] = -1;
+        }
+      }
+    }
+    
     /**
-     * Stop recording dependencies.
+     * @return Fragment number of the left over fragment.
      */
-    void close();
+    public int getLeftoverFragmentIndex() {
+      return getNumFragments() - 1;
+    }
+    
+    /**
+     * @return Number of code fragments in the compilation. Leftover fragment and initial fragment.
+     */
+    public int getNumFragments() {
+      return fragmentToSplitPoint.length;
+    }
 
     /**
-     * Stop recording the current dependency graph.
+     * @return One of the split point number in a given fragment. If there
+     *     are more than one splitpoints in the a fragment, -1 is returned.
      */
-    void endDependencyGraph();
-
-    void open();
-
-    /**
-     * Start a new dependency graph. It can be an extension of a previously
-     * recorded dependency graph, in which case the dependencies in the previous
-     * graph will not be repeated.
-     */
-    void startDependencyGraph(String name, String extnds);
+    public int getSplitPointFromFragmnet(int fragment) {
+      return fragmentToSplitPoint[fragment];
+    }
   }
   
   /**
@@ -133,6 +156,68 @@
      */
     EDGE_GREEDY,
   }
+
+  /**
+   * A map from program atoms to the split point, if any, that they are
+   * exclusive to. Atoms not exclusive to any split point are either mapped to 0
+   * or left out of the map entirely. Note that the map is incomplete; any entry
+   * not included has not been proven to be exclusive. Also, note that the
+   * initial load sequence is assumed to already be loaded.
+   */
+  private static class ExclusivityMap {
+    public Map<JField, Integer> fields = new HashMap<JField, Integer>();
+    public Map<JMethod, Integer> methods = new HashMap<JMethod, Integer>();
+    public Map<String, Integer> strings = new HashMap<String, Integer>();
+    public Map<JDeclaredType, Integer> types = new HashMap<JDeclaredType, Integer>();
+  }
+  
+  /**
+   * A liveness predicate that is based on an exclusivity map.
+   */
+  private static class ExclusivityMapLivenessPredicate implements LivenessPredicate {
+    private final int fragment;
+    private final ExclusivityMap fragmentMap;
+
+    public ExclusivityMapLivenessPredicate(ExclusivityMap fragmentMap, int fragment) {
+      this.fragmentMap = fragmentMap;
+      this.fragment = fragment;
+    }
+
+    @Override
+    public boolean isLive(JDeclaredType type) {
+      return checkMap(fragmentMap.types, type);
+    }
+
+    @Override
+    public boolean isLive(JField field) {
+      return checkMap(fragmentMap.fields, field);
+    }
+
+    @Override
+    public boolean isLive(JMethod method) {
+      return checkMap(fragmentMap.methods, method);
+    }
+
+    @Override
+    public boolean isLive(String literal) {
+      return checkMap(fragmentMap.strings, literal);
+    }
+
+    @Override
+    public boolean miscellaneousStatementsAreLive() {
+      return true;
+    }
+
+    private <T> boolean checkMap(Map<T, Integer> map, T x) {
+      Integer entryForX = map.get(x);
+      if (entryForX == null) {
+        // unrecognized items are always live
+        return true;
+      } else {
+        return (fragment == entryForX) || (entryForX == 0);
+      }
+    }
+  }
   
   /**
    * Maps an atom to a set of split point that can be live (NOT necessary exclusively)
@@ -180,87 +265,19 @@
   }
   
   /**
-   * LivenessPredicate to tell the fragment extractor what is live.
-   */
-  private static class LiveSplitPointsPredicate implements LivenessPredicate {
-    private final LiveSplitPointMap map;
-    private final Predicate<BitSet> mask;
-    LiveSplitPointsPredicate(LiveSplitPointMap map, Predicate<BitSet> mask) {
-      this.map = map;
-      this.mask = mask;
-    }
-    
-    @Override
-    public boolean isLive(JDeclaredType type) {
-      return isLive(map.types, type);
-    }
-
-    @Override
-    public boolean isLive(JField field) {
-      return isLive(map.fields, field);
-    }
-    
-    @Override
-    public boolean isLive(JMethod method) {
-      return isLive(map.methods, method);
-    }
-    
-    @Override
-    public boolean isLive(String literal) {
-      return isLive(map.strings, literal);
-    }
-    
-    @Override
-    public boolean miscellaneousStatementsAreLive() {
-      return true;
-    }
-    
-    private <T> boolean isLive(Map<T, BitSet> map, T atom) {
-      BitSet value = map.get(atom);
-      return value != null && mask.apply(value);
-    }
-  }
-  
-  /**
-   * A {@link MultipleDependencyGraphRecorder} that does nothing.
-   */
-  public static final MultipleDependencyGraphRecorder NULL_RECORDER =
-      new MultipleDependencyGraphRecorder() {
-        public void close() {
-        }
-
-        public void endDependencyGraph() {
-        }
-
-        public void methodIsLiveBecause(JMethod liveMethod, ArrayList<JMethod> dependencyChain) {
-        }
-
-        public void open() {
-        }
-
-        public void startDependencyGraph(String name, String extnds) {
-        }
-  };
-
-  /**
-   * Number of split points to merge, this should be configurable by user later.
-   */
-  public static final int NUM_SPLITPOINTS_TO_MERGE = 12;
-  
-  /**
    * The property key for a list of initially loaded split points.
    */
   private static final String PROP_INITIAL_SEQUENCE = "compiler.splitpoint.initial.sequence";
   
   public static ControlFlowAnalyzer computeInitiallyLive(JProgram jprogram) {
-    return computeInitiallyLive(jprogram, NULL_RECORDER);
+    return computeInitiallyLive(jprogram, CodeSplitter.NULL_RECORDER);
   }
-  
+
   public static ControlFlowAnalyzer computeInitiallyLive(
       JProgram jprogram, MultipleDependencyGraphRecorder dependencyRecorder) {
-    // Control Flow Analysis from a split point.
-    ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram);    
     dependencyRecorder.startDependencyGraph("initial", null);
+    
+    ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram); 
     cfa.setDependencyRecorder(dependencyRecorder);
     cfa.traverseEntryMethods();
     traverseClassArray(jprogram, cfa);
@@ -268,15 +285,17 @@
     dependencyRecorder.endDependencyGraph();
     return cfa;
   }
-  
+
   public static void exec(TreeLogger logger, JProgram jprogram, JsProgram jsprogram,
-      JavaToJavaScriptMap map, MultipleDependencyGraphRecorder dependencyRecorder) {
+      JavaToJavaScriptMap map, int fragmentsToMerge,
+      MultipleDependencyGraphRecorder dependencyRecorder) {
     if (jprogram.getRunAsyncs().size() == 0) {
       // Don't do anything if there is no call to runAsync
       return;
     }
     Event codeSplitterEvent = SpeedTracerLogger.start(CompilerEventType.CODE_SPLITTER);
-    new CodeSplitter2(logger, jprogram, jsprogram, map, dependencyRecorder).execImpl();
+    new CodeSplitter2(
+        logger, jprogram, jsprogram, map, fragmentsToMerge, dependencyRecorder).execImpl();
     codeSplitterEvent.end();
   }
   
@@ -298,8 +317,10 @@
         throw new UnableToCompleteException();
       }
       final String lookupErrorHolder[] = new String[1];
+      @SuppressWarnings("deprecation")
       JNode referent =
           JsniRefLookup.findJsniRefTarget(jsniRef, program, new JsniRefLookup.ErrorReporter() {
+            @Override
             public void reportError(String error) {
               lookupErrorHolder[0] = error;
             }
@@ -346,7 +367,7 @@
     codeSplitterEvent.end();
     return result;
   }
-
+  
   /**
    * Choose an initial load sequence of split points for the specified program.
    * Do so by identifying split points whose code always load first, before any
@@ -356,6 +377,7 @@
    * 
    * @throws UnableToCompleteException If the module specifies a bad load order
    */
+  @SuppressWarnings("javadoc")
   public static void pickInitialLoadSequence(TreeLogger logger, JProgram program,
       Properties properties) throws UnableToCompleteException {
     Event codeSplitterEvent =
@@ -391,6 +413,7 @@
     program.setSplitPointInitialSequence(new ArrayList<Integer>(initialLoadSequence));
     codeSplitterEvent.end();
   }
+  
   private static Map<JField, JClassLiteral> buildFieldToClassLiteralMap(JProgram jprogram) {
     final Map<JField, JClassLiteral> map = new HashMap<JField, JClassLiteral>();
     class BuildFieldToLiteralVisitor extends JVisitor {
@@ -402,6 +425,7 @@
     (new BuildFieldToLiteralVisitor()).accept(jprogram);
     return map;
   }
+  
   private static <T> void countShardedAtomsOfType(Map<T, BitSet> livenessMap, int[][] matrix) {
     // Count the number of atoms shared only by 
     for (Entry<T, BitSet> fieldLiveness : livenessMap.entrySet()) {
@@ -419,7 +443,20 @@
       matrix[start][end]++;
     }
   }
-
+  
+  /**
+   * Extract the types from a set that happen to be declared types.
+   */
+  private static Set<JDeclaredType> declaredTypesIn(Set<JReferenceType> types) {
+    Set<JDeclaredType> result = new HashSet<JDeclaredType>();
+    for (JReferenceType type : types) {
+      if (type instanceof JDeclaredType) {
+        result.add((JDeclaredType) type);
+      }
+    }
+    return result;
+  }
+  
   private static <T> int getOrZero(Map<T, BitSet> map, T key) {
     BitSet value = map.get(key);
     if (value != null && value.cardinality() == 1) {
@@ -427,7 +464,7 @@
     }
     return 0;
   }
- 
+  
   /**
    * Installs the initial load sequence into AsyncFragmentLoader.BROWSER_LOADER.
    * The initializer looks like this:
@@ -449,7 +486,7 @@
     assert ((JNewArray) arg1).getArrayType() == arrayType;
     List<JExpression> initializers = new ArrayList<JExpression>(initialLoadSequence.size());
     for (int sp : initialLoadSequence) {
-      initializers.add(JIntLiteral.get(sp));
+      initializers.add(new JNumericEntry(call.getSourceInfo(), "RunAsyncFragmentIndex", sp));
     }
     JNewArray newArray =
         JNewArray.createInitializers(arg1.getSourceInfo(), arrayType, Lists
@@ -469,9 +506,7 @@
     }
     
     for (JField node : cfa.getFieldsWritten()) {
-      if (node instanceof JField) {
-        liveness.setLive((JField) node, idx);
-      }  
+      liveness.setLive(node, idx);
     }
     
     for (String s : cfa.getLiveStrings()) {
@@ -522,7 +557,7 @@
       }
     }
   }
-  
+
   /**
    * Any immortal codegen types must be part of the initial download.
    */
@@ -538,8 +573,33 @@
     }
   }
 
-  private final Map<JField, JClassLiteral> fieldToLiteralOfClass;
+  private static <T> Set<T> union(Set<? extends T> set1, Set<? extends T> set2) {
+    Set<T> union = new HashSet<T>();
+    union.addAll(set1);
+    union.addAll(set2);
+    return union;
+  }
 
+  private static <T> void updateReverseMap(int entry, Map<T, Integer> map, Set<?> liveWithoutEntry,
+      Iterable<T> all) {
+    for (T each : all) {
+      if (!liveWithoutEntry.contains(each)) {
+        /*
+         * Note that it is fine to overwrite a preexisting entry in the map. If
+         * an atom is dead until split point i has been reached, and is also
+         * dead until entry j has been reached, then it is dead until both have
+         * been reached. Thus, it can be downloaded along with either i's or j's
+         * code.
+         */
+        map.put(each, entry);
+      }
+    }
+  }
+
+  ExclusivityMap fragmentMap = new ExclusivityMap();
+ 
+  private final Map<JField, JClassLiteral> fieldToLiteralOfClass;
+  
   private FragmentExtractor fragmentExtractor;
   
   /**
@@ -561,6 +621,17 @@
   private final Set<JMethod> methodsInJavaScript;
   
   /**
+   * Number of split points to merge.
+   */
+  private final int splitPointsMerge;
+  
+  /**
+   * Maps the split point index X to Y where where that split point X would
+   * appear in the Y.cache.js
+   */
+  private final int[] splitPointToCodeIndexMap;
+
+  /**
    * Maps a split-point number to a fragment number.
    * 
    * splitPointToFragmmentMap[x] = y implies split point #x is in fragment #y.
@@ -571,9 +642,11 @@
   private final int[] splitPointToFragmentMap;
 
   private CodeSplitter2(TreeLogger logger, JProgram jprogram, JsProgram jsprogram,
-      JavaToJavaScriptMap map, MultipleDependencyGraphRecorder dependencyRecorder) {
+      JavaToJavaScriptMap map, int splitPointsMerge,
+      MultipleDependencyGraphRecorder dependencyRecorder) {
     this.jprogram = jprogram;
     this.jsprogram = jsprogram;
+    this.splitPointsMerge = splitPointsMerge;
     this.fragmentExtractor = new FragmentExtractor(jprogram, jsprogram, map);
     this.initialLoadSequence = new LinkedHashSet<Integer>(jprogram.getSplitPointInitialSequence());
     
@@ -583,6 +656,11 @@
       splitPointToFragmentMap[i] = i;
     }
     
+    this.splitPointToCodeIndexMap = new int[jprogram.getRunAsyncs().size() + 1];
+    for (int i = 0; i < splitPointToCodeIndexMap.length; i++) {
+      splitPointToCodeIndexMap[i] = 0;
+    }
+    
     // TODO(acleung): I don't full understand this. This is mostly from the old
     // algorithm which patches up certain dependency after the control flow analysis.
     fieldToLiteralOfClass = buildFieldToClassLiteralMap(jprogram);
@@ -612,6 +690,31 @@
     fragmentStats.put(splitPoint, stats);
   }
   
+  private ControlFlowAnalyzer computeAllButNCfas(
+      ControlFlowAnalyzer liveAfterInitialSequence, List<Integer> sp) {
+    List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>();
+    ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(liveAfterInitialSequence);
+    for (JRunAsync otherRunAsync : jprogram.getRunAsyncs()) {
+      if (isInitial(otherRunAsync.getSplitPoint())) {
+        continue;
+      }
+      if (sp.contains(otherRunAsync.getSplitPoint())) {
+        continue;
+      }
+      cfa.traverseFromRunAsync(otherRunAsync);
+    }
+    return cfa;
+  }
+
+  /**
+   * Compute a CFA that covers the entire live code of the program.
+   */
+  private ControlFlowAnalyzer computeCompleteCfa() {
+    ControlFlowAnalyzer everything = new ControlFlowAnalyzer(jprogram);
+    everything.traverseEverything();
+    return everything;
+  }
+  
   private ControlFlowAnalyzer computeLiveSet(
       ControlFlowAnalyzer initiallyLive, LiveSplitPointMap liveness, JRunAsync runAsync) {
     // Control Flow Analysis from a split point.
@@ -628,7 +731,7 @@
     
     // Step #1: Compute all the initially live atoms that are part of entry points
     // class inits..etc.
-    initiallyLive = computeInitiallyLive(jprogram, NULL_RECORDER);
+    initiallyLive = computeInitiallyLive(jprogram, CodeSplitter.NULL_RECORDER);
     recordLiveSet(initiallyLive, liveness, 0);
  
     // Step #2: Incrementally add each split point that are classified as initial load sequence.
@@ -656,7 +759,10 @@
     partitionFragments();
     
     // Step #6: Extract fragments using the partition algorithm.
-    extractStatements(initiallyLive);    
+    extractStatements(computeInitiallyLive(jprogram, CodeSplitter.NULL_RECORDER));
+    
+    // Step #7: Replaces the splitpoint number with the new fragment number.
+    replaceFragmentId();
   }
   
   private void extractStatements(ControlFlowAnalyzer initiallyLive) {
@@ -670,29 +776,44 @@
       addFragment(0, alreadyLoaded, liveNow, noStats, fragmentStats);
     }
     
-    final List<Predicate<BitSet>> exclusivePredicates = new LinkedList<Predicate<BitSet>>();
-    
-    // Signifies what has been already loaded in the initial fragment.
-    LivenessPredicate alreadyLoaded = new LiveSplitPointsPredicate(liveness, new Predicate<BitSet>() {
-      @Override
-      public boolean apply(BitSet value) {
-        // Live if it is used by the first fragment.
-        if (value.get(0)) {
-          return true;
-        } else {            
-          for (int sp : initialLoadSequence) {
-            if (value.get(sp)) {
-              return true;
-            }
-          }
-        }
-        return false;
+    ControlFlowAnalyzer liveAfterInitialSequence = new ControlFlowAnalyzer(initiallyLive);
+   
+    int cacheIndex = 1;
+    // Initial Split Point.
+    {      
+      for (final int sp : initialLoadSequence) {
+        splitPointToCodeIndexMap[sp] = cacheIndex;        
+        LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(liveAfterInitialSequence);
+        ControlFlowAnalyzer liveAfterSp = new ControlFlowAnalyzer(liveAfterInitialSequence);
+        JRunAsync runAsync = jprogram.getRunAsyncs().get(sp - 1);
+        liveAfterSp.traverseFromRunAsync(runAsync);
+        LivenessPredicate liveNow = new CfaLivenessPredicate(liveAfterSp);
+        List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex);
+        addFragment(sp, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
+        liveAfterInitialSequence = liveAfterSp;       
+        cacheIndex++;
       }
-    });
+    }
 
+    ControlFlowAnalyzer everything = computeCompleteCfa();
+    Set<JField> allFields = new HashSet<JField>();
+    Set<JMethod> allMethods = new HashSet<JMethod>();
+
+    for (JNode node : everything.getLiveFieldsAndMethods()) {
+      if (node instanceof JField) {
+        allFields.add((JField) node);
+      }
+      if (node instanceof JMethod) {
+        allMethods.add((JMethod) node);
+      }
+    }
+    allFields.addAll(everything.getFieldsWritten());
+    
     // Search for all the atoms that are exclusively needed in each split point.
     for (int i = 1; i < splitPointToFragmentMap.length; i++) {
       
+      ArrayList<Integer> splitPoints = new ArrayList<Integer>();
+      
       // This mean split point [i] has been merged with another split point, ignore it.
       if (splitPointToFragmentMap[i] != i) {
         continue;
@@ -703,64 +824,54 @@
         continue;
       }
       
-      // Creates a mask that is used to check if an atom is exclusively needed by the current
-      // fragment as well as all the fragment is was merged with.
-      final BitSet mask = new BitSet(splitPointToFragmentMap.length);
-      mask.set(i);
+      splitPoints.add(i);
+      splitPointToCodeIndexMap[i] = cacheIndex;
+      
+      
       for (int j = i + 1; j < splitPointToFragmentMap.length; j++) {
         if (initialLoadSequence.contains(j)) {
           continue;
         }
         if (splitPointToFragmentMap[j] == i) {
-          mask.set(j);
+          splitPointToCodeIndexMap[j] = cacheIndex;
+          splitPoints.add(j);
         }
       }
-      
-      // Creates a predicate
-      final Predicate<BitSet> pred = new Predicate<BitSet>() {
-        @Override
-        public boolean apply(BitSet value) {
-          if (value.get(0)) {
-            return true;
-          }
 
-          for (int sp : initialLoadSequence) {
-            if (value.get(sp)) {
-              return true;
-            }
-          }
-          
-          BitSet valueOrMask = (BitSet) value.clone();
-          valueOrMask.or(mask);
-          
-          // Returns true if it at least matches one of the set field in the mask but not
-          // having a field set that is unset in the mask.
-          return value.intersects(mask) && valueOrMask.cardinality() <= mask.cardinality();
-        }
-      };
-      exclusivePredicates.add(pred);
+      ControlFlowAnalyzer allButOne = computeAllButNCfas(liveAfterInitialSequence, splitPoints);
+      Set<JNode> allLiveNodes =
+          union(allButOne.getLiveFieldsAndMethods(), allButOne.getFieldsWritten());
+      updateReverseMap(i, fragmentMap.fields, allLiveNodes, allFields);
+      updateReverseMap(i, fragmentMap.methods, allButOne.getLiveFieldsAndMethods(), allMethods);
+      updateReverseMap(i, fragmentMap.strings, allButOne.getLiveStrings(), everything
+          .getLiveStrings());
+      updateReverseMap(i, fragmentMap.types, declaredTypesIn(allButOne.getInstantiatedTypes()),
+          declaredTypesIn(everything.getInstantiatedTypes()));
+
+      // This mean split point [i] has been merged with another split point, ignore it.
+      if (splitPointToFragmentMap[i] != i) {
+        continue;
+      }
       
-      LivenessPredicate liveNow = new LiveSplitPointsPredicate(liveness, pred);
-      List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(i);
+      // This was needed in the initial load sequence, ignore it.
+      if (initialLoadSequence.contains(i)) {
+        continue;
+      }
+
+      LivenessPredicate alreadyLoaded = new ExclusivityMapLivenessPredicate(fragmentMap, 0);
+      LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, i);
+      List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex);
       addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
+      cacheIndex++;
     }
-    
-    // Left over fragments.
-    {      
-      LivenessPredicate liveNow = new LiveSplitPointsPredicate(liveness, new Predicate<BitSet>() {
 
-        @Override
-        public boolean apply(BitSet value) {
-          for (Predicate<BitSet> p : exclusivePredicates) {
-            if (p.apply(value)) {
-              return false;
-            }
-          }
-          return true;
-        }
-      });
-      
-      List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(splitPointToFragmentMap.length);
+    /*
+     * Compute the leftovers fragment.
+     */
+    {
+      LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(liveAfterInitialSequence);
+      LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, 0);
+      List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex);
       addFragment(splitPointToFragmentMap.length, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
     }
     
@@ -773,16 +884,17 @@
       fragBlock.getStatements().addAll(fragmentStats.get(i));
     }
 
-    // TODO(acleung): This is an hack-ish way to put share the fragment map.
-    // jprogram.splitPointToFragmentMap = splitPointToFragmentMap;
+    jprogram.setFragmentPartitioningResult(
+        new FragmentPartitioningResult(splitPointToCodeIndexMap, fragmentStats.size()));
   }
-
+  
   private void fixUpLoadOrderDependencies(LiveSplitPointMap fragmentMap, int splitPoint) {
     fixUpLoadOrderDependenciesForMethods(fragmentMap, splitPoint);
     fixUpLoadOrderDependenciesForTypes(fragmentMap, splitPoint);
     fixUpLoadOrderDependenciesForClassLiterals(fragmentMap, splitPoint);
     fixUpLoadOrderDependenciesForFieldsInitializedToStrings(fragmentMap, splitPoint);
   }
+  
   private void fixUpLoadOrderDependenciesForClassLiterals(LiveSplitPointMap fragmentMap, int splitPoint) {
     int numClassLitStrings = 0;
     int numFixups = 0;
@@ -825,7 +937,6 @@
       }
     }
   }
-  
   private void fixUpLoadOrderDependenciesForMethods(LiveSplitPointMap fragmentMap, int splitPoint) {
     int numFixups = 0;
 
@@ -870,6 +981,10 @@
       }
     }
   }
+  
+  private boolean isInitial(int entry) {
+    return initialLoadSequence.contains(entry);
+  }
 
   /**
    * We haves pinned down that fragment partition is an NP-Complete problem that maps right to
@@ -895,7 +1010,7 @@
     countShardedAtomsOfType(liveness.strings, matrix);
     countShardedAtomsOfType(liveness.types, matrix);
 
-    for (int c = 0; c < NUM_SPLITPOINTS_TO_MERGE; c++) {
+    for (int c = 0; c < splitPointsMerge; c++) {
       int bestI = 0, bestJ = 0, max = 0;
       for (int i = 1; i < splitPointToFragmentMap.length; i++) {
         if (initialLoadSequence.contains(i)) {
@@ -931,7 +1046,21 @@
       }
     }
   }
-  
+
+  private void replaceFragmentId() {
+    (new JsModVisitor() {
+      @Override
+      public void endVisit(JsNumericEntry x, JsContext ctx) {
+        if (x.getKey().equals("RunAsyncFragmentIndex")) {
+          x.setValue(splitPointToCodeIndexMap[x.getValue()]);
+        }
+        if (x.getKey().equals("RunAsyncFragmentCount")) {
+          x.setValue(jsprogram.getFragmentCount() - 1);
+        }
+      }
+    }).accept(jsprogram);
+  }
+
   /**
    * Traverse <code>exp</code> and find all string literals within it.
    */
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 7120ab7..8c42325 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
@@ -15,10 +15,10 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
-import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.linker.CastableTypeMap;
 import com.google.gwt.core.ext.linker.impl.StandardCastableTypeMap;
 import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
+import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.JsOutputOption;
@@ -68,6 +68,7 @@
 import com.google.gwt.dev.jjs.ast.JNewArray;
 import com.google.gwt.dev.jjs.ast.JNewInstance;
 import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JNumericEntry;
 import com.google.gwt.dev.jjs.ast.JParameter;
 import com.google.gwt.dev.jjs.ast.JParameterRef;
 import com.google.gwt.dev.jjs.ast.JPostfixOperation;
@@ -130,6 +131,7 @@
 import com.google.gwt.dev.js.ast.JsNormalScope;
 import com.google.gwt.dev.js.ast.JsNullLiteral;
 import com.google.gwt.dev.js.ast.JsNumberLiteral;
+import com.google.gwt.dev.js.ast.JsNumericEntry;
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
 import com.google.gwt.dev.js.ast.JsParameter;
 import com.google.gwt.dev.js.ast.JsPostfixOperation;
@@ -1248,7 +1250,12 @@
       popList(newOp.getArguments(), x.getArgs().size()); // args
       push(newOp);
     }
-
+    
+    @Override
+    public void endVisit(JNumericEntry x, Context ctx) {
+      push(new JsNumericEntry(x.getSourceInfo(), x.getKey(), x.getValue()));
+    }
+    
     @Override
     public void endVisit(JParameter x, Context ctx) {
       push(new JsParameter(x.getSourceInfo(), names.get(x)));
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 3543d4e..88cfd1c 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
@@ -264,7 +264,7 @@
         // The target method was already pruned (TypeTightener will fix this).
         return;
       }
-
+  
       // Let's do it!
       toBeMadeStatic.add(method);
     }
@@ -288,7 +288,8 @@
     public void endVisit(JMethodCall x, Context ctx) {
       JMethod oldMethod = x.getTarget();
       JMethod newMethod = program.getStaticImpl(oldMethod);
-      if (newMethod == null || x.canBePolymorphic()) {
+      
+      if (newMethod == null || x.canBePolymorphic()) {        
         return;
       }
 
@@ -302,6 +303,7 @@
          */
         return;
       }
+
       ctx.replaceMe(makeStaticCall(x, newMethod));
     }
 
@@ -313,7 +315,7 @@
 
     @Override
     public boolean visit(JProgram x, Context ctx) {
-      initiallyLive = CodeSplitter.computeInitiallyLive(x);
+      initiallyLive = CodeSplitter2.computeInitiallyLive(x);
       return true;
     }
   }
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 7d50f49..2e9222a 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,10 +24,10 @@
 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.JNumericEntry;
 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
@@ -100,7 +100,7 @@
         JMethod runAsyncMethod = program.getIndexedMethod("AsyncFragmentLoader.runAsync");
         assert runAsyncMethod != null;
         JMethodCall runAsyncCall = new JMethodCall(info, null, runAsyncMethod);
-        runAsyncCall.addArg(JIntLiteral.get(splitPoint));
+        runAsyncCall.addArg(new JNumericEntry(info, "RunAsyncFragmentIndex", splitPoint));
         runAsyncCall.addArg(asyncCallback);
 
         JReferenceType callbackType = (JReferenceType) asyncCallback.getType();
@@ -188,7 +188,7 @@
         JMethodCall newCall =
             new JMethodCall(info, null, program
                 .getIndexedMethod("RunAsyncCode.forSplitPointNumber"));
-        newCall.addArg(program.getLiteralInt(splitPoint));
+        newCall.addArg(new JNumericEntry(info, "RunAsyncFragmentIndex", splitPoint));
         ctx.replaceMe(newCall);
       }
     }
@@ -269,6 +269,7 @@
   private void setNumEntriesInAsyncFragmentLoader(int entryCount) {
     JMethodCall constructorCall = getBrowserLoaderConstructor(program);
     assert constructorCall.getArgs().get(0).getType() == JPrimitiveType.INT;
-    constructorCall.setArg(0, program.getLiteralInt(entryCount));
+    constructorCall.setArg(0,
+        new JNumericEntry(constructorCall.getSourceInfo(), "RunAsyncFragmentCount", entryCount));
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsHoister.java b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
index 2917644..22ebc87 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsHoister.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
@@ -30,6 +30,7 @@
 import com.google.gwt.dev.js.ast.JsNew;
 import com.google.gwt.dev.js.ast.JsNullLiteral;
 import com.google.gwt.dev.js.ast.JsNumberLiteral;
+import com.google.gwt.dev.js.ast.JsNumericEntry;
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
 import com.google.gwt.dev.js.ast.JsPostfixOperation;
 import com.google.gwt.dev.js.ast.JsPrefixOperation;
@@ -172,6 +173,11 @@
     public void endVisit(JsNumberLiteral x, JsContext ctx) {
       stack.push(x);
     }
+    
+    @Override
+    public void endVisit(JsNumericEntry x, JsContext ctx) {
+      stack.push(x);
+    }
 
     @Override
     public void endVisit(JsObjectLiteral x, JsContext ctx) {
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index 564191c..a10de34 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -48,6 +48,7 @@
 import com.google.gwt.dev.js.ast.JsNew;
 import com.google.gwt.dev.js.ast.JsNullLiteral;
 import com.google.gwt.dev.js.ast.JsNumberLiteral;
+import com.google.gwt.dev.js.ast.JsNumericEntry;
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
 import com.google.gwt.dev.js.ast.JsOperator;
 import com.google.gwt.dev.js.ast.JsParameter;
@@ -781,6 +782,12 @@
     }
     return false;
   }
+  
+  @Override
+  public boolean visit(JsNumericEntry x, JsContext ctx) {
+    p.print(Integer.toString(x.getValue()));
+    return false;
+  }
 
   @Override
   public boolean visit(JsObjectLiteral x, JsContext ctx) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNumericEntry.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNumericEntry.java
new file mode 100644
index 0000000..68e0025
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNumericEntry.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js.ast;
+
+import com.google.gwt.dev.jjs.SourceInfo;
+
+/**
+ * Represent an index that can be replacable by the compiler at compile time.
+ */
+public final class JsNumericEntry extends JsExpression {
+  private final String key;
+  private int value;
+  
+  public JsNumericEntry(SourceInfo info, String key, int value) {
+    super(info);
+    this.key = key;
+    this.value = value;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  @Override
+  public NodeKind getKind() {
+    return NodeKind.NUMBER;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  @Override
+  public boolean hasSideEffects() {
+    return false;
+  }
+  
+  @Override
+  public boolean isDefinitelyNotNull() {
+    return true;
+  }
+
+  @Override
+  public boolean isDefinitelyNull() {
+    return false;
+  }
+
+  public void setValue(int value) {
+    this.value = value;
+  }
+
+  @Override
+  public void traverse(JsVisitor v, JsContext ctx) {
+    v.visit(this, ctx);
+    v.endVisit(this, ctx);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java b/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java
index ba5396c..77e27fb 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java
@@ -202,6 +202,9 @@
 
   public void endVisit(JsNumberLiteral x, JsContext ctx) {
   }
+  
+  public void endVisit(JsNumericEntry x, JsContext ctx) {
+  }
 
   public void endVisit(JsObjectLiteral x, JsContext ctx) {
   }
@@ -361,6 +364,10 @@
   public boolean visit(JsNumberLiteral x, JsContext ctx) {
     return true;
   }
+  
+  public boolean visit(JsNumericEntry x, JsContext ctx) {
+    return true;
+  }
 
   public boolean visit(JsObjectLiteral x, JsContext ctx) {
     return true;
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerFragmentMerge.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerFragmentMerge.java
new file mode 100644
index 0000000..fde83e1
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerFragmentMerge.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerInt;
+
+/**
+ * An ArgHandler to provide the -XfragmentMerge flag.
+ */
+public class ArgHandlerFragmentMerge extends ArgHandlerInt {
+
+  private final OptionFragmentsMerge option;
+
+  public ArgHandlerFragmentMerge(OptionFragmentsMerge option) {
+    this.option = option;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "EXPERIMENTAL: Enables Fragment merging code splitter.";
+  }
+
+  @Override
+  public String getTag() {
+    return "-XfragmentMerge";
+  }
+
+  @Override
+  public String[] getTagArgs() {
+    return new String[] {"numFragments"};
+  }
+
+  @Override
+  public void setInt(int value) {
+    option.setFragmentsMerge(value);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionFragmentsMerge.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionFragmentsMerge.java
new file mode 100644
index 0000000..42d4ab1
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionFragmentsMerge.java
@@ -0,0 +1,35 @@
+/*
+ * 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.util.arg;
+
+/**
+ * Enable the new code splitter that auto-partitions.
+ */
+public interface OptionFragmentsMerge {
+  
+  // TODO(acleung): This is currently an experimental frag. We should find a
+  // use case new splitter. Some possible approache:
+  //
+  // 1. Magically decide the number of fragments to merge. (May be too hard)
+  // 2. All the user to specify number of fragments they want to *keep* instead
+  //    of the number they want to merge.
+  // 3. Ask the user what is the max (average) size of fragments. (This
+  //    can only be an estimated.
+  
+  int getFragmentsMerge();
+
+  void setFragmentsMerge(int numFragments);
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java b/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java
index 9fa40e1..b14b839 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java
@@ -217,7 +217,7 @@
     JavaToJavaScriptMap map = GenerateJavaScriptAST.exec(
         jProgram, jsProgram, JsOutputOption.PRETTY, symbolTable, new PropertyOracle[]{
             new StaticPropertyOracle(orderedProps, orderedPropValues, configProps)});
-    CodeSplitter2.exec(logger, jProgram, jsProgram, map, null);    
+    CodeSplitter2.exec(logger, jProgram, jsProgram, map, 4, null);    
   }
   
   private static String createRunAsync(String body) {