This patch modifies the compiler in two ways:
1. It can shard out precompiles during parallel builds.
2. Linkers can indicate which part should happen per-shard and which part should
happen on the final link node.

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

Review by: cromwellian


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7650 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/Linker.java b/dev/core/src/com/google/gwt/core/ext/Linker.java
index 5dc6ff1..55e49d5 100644
--- a/dev/core/src/com/google/gwt/core/ext/Linker.java
+++ b/dev/core/src/com/google/gwt/core/ext/Linker.java
@@ -16,23 +16,34 @@
 package com.google.gwt.core.ext;
 
 import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.Shardable;
 
 /**
  * Defines a linker for the GWT compiler. Each Linker must be annotated with a
  * {@link com.google.gwt.core.ext.linker.LinkerOrder} annotation to determine
  * the relative ordering of the Linkers. Exact order of Linker execution will be
  * determined by the order of <code>add-linker</code> tags in the module
- * configuration.
+ * configuration. Each Linker should also be annotated with {@link Shardable};
+ * non-shardable linkers are deprecated and will eventually not be supported.
  * 
  * <p>
  * A new instance of a linker is created each time a module is compiled or
  * during hosted mode when a module first loads (or is refreshed). During a
- * compile, {@link #link(TreeLogger, LinkerContext, ArtifactSet)} will be called
- * exactly once, and the artifact set will contain any and all generated
- * artifacts. . In hosted mode,
- * {@link #link(TreeLogger, LinkerContext, ArtifactSet)} is called initially,
- * but with no generated artifacts. If any artifacts are subsequently generated
- * during the course of running hosted mode,
+ * compile, {@link #link(TreeLogger, LinkerContext, ArtifactSet)} is called
+ * exactly once on each non-shardable linker, and the artifact set will contain
+ * any and all generated artifacts. For shardable linkers,
+ * {@link #link(TreeLogger, LinkerContext, ArtifactSet, boolean)} is called once
+ * for each compiled permutation and once after all compiles are finished. The
+ * precise artifacts supplied differ with each call and are described in the
+ * method's documentation.
+ * 
+ * <p>
+ * When hosted mode starts for a module, it calls
+ * {@link #link(TreeLogger, LinkerContext, ArtifactSet)} for non-shardable
+ * linkers and {@link #link(TreeLogger, LinkerContext, ArtifactSet, boolean)}
+ * for shardable ones, passing <code>false</code> as the
+ * <code>onePermutation</code> argument. If any artifacts are subsequently
+ * generated during the course of running hosted mode,
  * {@link #relink(TreeLogger, LinkerContext, ArtifactSet)} will be called with
  * the new artifacts.
  * </p>
@@ -44,7 +55,17 @@
   public abstract String getDescription();
 
   /**
-   * Invoke the Linker.
+   * Check whether this class has the {@link Shardable} annotation.
+   */
+  public final boolean isShardable() {
+    return getClass().isAnnotationPresent(Shardable.class);
+  }
+
+  /**
+   * This method is invoked for linkers not annotated with {@link Shardable}. It
+   * sees all artifacts across the whole compile and can modify them
+   * arbitrarily. This method is only called if the linker is not annotated with
+   * {@link Shardable}.
    * 
    * @param logger the TreeLogger to record to
    * @param context provides access to the Linker's environment
@@ -53,8 +74,44 @@
    * @throws UnableToCompleteException if compilation violates assumptions made
    *           by the Linker or for errors encountered by the Linker
    */
-  public abstract ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException;
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
+    assert !isShardable();
+    return artifacts;
+  }
+
+  /**
+   * <p>
+   * This method is invoked for linkers annotated with {@link Shardable}. It is
+   * called at two points during compilation: after the compile of each
+   * permutation, and after all compilation has finished. The
+   * <code>onePermutation</code> is <code>true</code> for a per-permutation call
+   * and <code>false</code> for a global final-link call.
+   * 
+   * <p>
+   * For one-permutation calls, this method is passed all artifacts generated
+   * for just the one permutation. For the global call at the end of
+   * compilation, this method sees artifacts for the whole compilation, but with
+   * two modifications intended to support builds on computer clusters:
+   * <ol>
+   * <li>All EmittedArtifacts have been converted to BinaryEmittedArtifacts
+   * <li>All artifacts not marked as
+   * {@link com.google.gwt.core.ext.linker.Transferable} have been discarded.
+   * </ol>
+   * 
+   * @param logger the TreeLogger to record to
+   * @param context provides access to the Linker's environment
+   * @param artifacts an unmodifiable view of the artifacts to link
+   * @return the artifacts that should be propagated through the linker chain
+   * @throws UnableToCompleteException if compilation violates assumptions made
+   *           by the Linker or for errors encountered by the Linker
+   */
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts, boolean onePermutation)
+      throws UnableToCompleteException {
+    assert isShardable();
+    return artifacts;
+  }
 
   /**
    * Re-invoke the Linker with newly generated artifacts. Linkers that need to
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java b/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java
index a54eaea..1e715f5 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java
@@ -30,8 +30,8 @@
  */
 public abstract class Artifact<C extends Artifact<C>> implements
     Comparable<Artifact<?>>, Serializable {
-  private final String linkerName;
   private transient Class<? extends Linker> linker;
+  private final String linkerName;
 
   /**
    * Constructor.
@@ -91,6 +91,14 @@
   @Override
   public abstract int hashCode();
 
+  /**
+   * Returns whether the {@link Transferable} annotation is present on this
+   * class. See {@link Transferable} for the implications.
+   */
+  public final boolean isTransferableFromShards() {
+    return getClass().isAnnotationPresent(Transferable.class);
+  }
+
   @Override
   public String toString() {
     return getClass().getName() + " created by " + getLinker().getName();
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/BinaryEmittedArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/BinaryEmittedArtifact.java
new file mode 100644
index 0000000..1828b1f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/BinaryEmittedArtifact.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2009 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.core.ext.linker;
+
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+
+/**
+ * <p>
+ * A thinned down version of some {@link EmittedArtifact}. Only its essentials,
+ * including name and contents, are available.
+ * </p>
+ * 
+ * <p>
+ * This class should only be extended within the GWT implementation.
+ * </p>
+ */
+public abstract class BinaryEmittedArtifact extends EmittedArtifact {
+  protected BinaryEmittedArtifact(String partialPath) {
+    super(StandardLinkerContext.class, partialPath);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
index 301c1ef..7ce343a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
@@ -16,6 +16,7 @@
 package com.google.gwt.core.ext.linker;
 
 import com.google.gwt.core.ext.Linker;
+import com.google.gwt.dev.Permutation;
 
 import java.util.Map;
 import java.util.SortedMap;
@@ -26,8 +27,12 @@
  * result in identical JavaScript.
  */
 public abstract class CompilationResult extends Artifact<CompilationResult> {
-  protected CompilationResult(Class<? extends Linker> linkerType) {
+  private final Permutation permutation;
+
+  protected CompilationResult(Class<? extends Linker> linkerType,
+      Permutation permutation) {
     super(linkerType);
+    this.permutation = permutation;
   }
 
   /**
@@ -35,17 +40,27 @@
    * the code that should be run when the application starts up. The remaining
    * elements are loaded via
    * {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback)
-   * GWT.runAsync}. See {@link com.google.gwt.core.client.impl.AsyncFragmentLoader
+   * GWT.runAsync}. See
+   * {@link com.google.gwt.core.client.impl.AsyncFragmentLoader
    * AsyncFragmentLoader} for details on the necessary linker support for
    * runAsync.
    */
   public abstract String[] getJavaScript();
 
   /**
+   * Return the permutation that compiled to this result.
+   */
+  public Permutation getPermutation() {
+    return permutation;
+  }
+
+  /**
    * Returns the permutation ID.
    */
-  public abstract int getPermutationId();
-  
+  public int getPermutationId() {
+    return getPermutation().getId();
+  }
+
   /**
    * Provides values for {@link SelectionProperty} instances that are not
    * explicitly set during the compilation phase. This method will return
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/Shardable.java b/dev/core/src/com/google/gwt/core/ext/linker/Shardable.java
new file mode 100644
index 0000000..03921d0
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/Shardable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.core.ext.linker;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation, when placed on a {@link com.google.gwt.core.ext.Linker}
+ * class, indicates that the linker supports the shardable version of the Linker
+ * API. Specifically, it implements
+ * {@link com.google.gwt.core.ext.Linker#link(com.google.gwt.core.ext.TreeLogger, com.google.gwt.core.ext.LinkerContext, ArtifactSet, boolean)}
+ * rather than
+ * {@link com.google.gwt.core.ext.Linker#link(com.google.gwt.core.ext.TreeLogger, com.google.gwt.core.ext.LinkerContext, ArtifactSet)}
+ * .
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Shardable {
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/Transferable.java b/dev/core/src/com/google/gwt/core/ext/linker/Transferable.java
new file mode 100644
index 0000000..1c50432
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/Transferable.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009 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.core.ext.linker;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An artifact marked with this annotation is passed through a compilation all
+ * the way to the final call to
+ * {@link com.google.gwt.core.ext.Linker#link(com.google.gwt.core.ext.TreeLogger, com.google.gwt.core.ext.LinkerContext, ArtifactSet, boolean)}
+ * . Try to minimize the number of artifacts so marked.
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Transferable {
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/BinaryOnlyArtifactWrapper.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/BinaryOnlyArtifactWrapper.java
new file mode 100644
index 0000000..3cf6fca
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/BinaryOnlyArtifactWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2009 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.BinaryEmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact;
+
+import java.io.InputStream;
+
+/**
+ * A wrapper around an emitted artifact that only allows reading the artifact's
+ * path and its binary contents.
+ */
+public class BinaryOnlyArtifactWrapper extends BinaryEmittedArtifact {
+  private final EmittedArtifact underlyingArtifact;
+
+  public BinaryOnlyArtifactWrapper(String path, EmittedArtifact artifact) {
+    super(path);
+    setPrivate(artifact.isPrivate());
+    this.underlyingArtifact = artifact;
+  }
+
+  @Override
+  public InputStream getContents(TreeLogger logger)
+      throws UnableToCompleteException {
+    return underlyingArtifact.getContents(logger);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeLinker.java
index d3e8f22..ced3eee 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeLinker.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeLinker.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.util.tools.Utility;
 
 import java.io.IOException;
@@ -27,6 +28,7 @@
  * This is a partial implementation of the Linker interface to support hosted
  * mode.
  */
+@Shardable
 public final class HostedModeLinker extends SelectionScriptLinker {
 
   public static String getHostedHtml() throws IOException {
@@ -74,8 +76,7 @@
   }
 
   @Override
-  protected String getSelectionScriptTemplate(TreeLogger logger,
-      LinkerContext context) throws UnableToCompleteException {
+  protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context) {
     return "com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js";
   }
 
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/JarEntryEmittedArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/JarEntryEmittedArtifact.java
new file mode 100644
index 0000000..1c1885e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/JarEntryEmittedArtifact.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2009 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.BinaryEmittedArtifact;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ *A <code>BinaryEmittedArtifact</code> that reads a jar entry.
+ */
+public class JarEntryEmittedArtifact extends BinaryEmittedArtifact {
+  /**
+   * An input stream that has its own open {@link JarFile}. When this stream is
+   * closed, the {@link JarFile} is as well.
+   */
+  private class JarEntryInputStream extends InputStream {
+    private JarFile jarFile;
+    private InputStream stream;
+
+    public JarEntryInputStream() throws IOException {
+      this.jarFile = new JarFile(file);
+      this.stream = jarFile.getInputStream(entry);
+    }
+
+    @Override
+    public void close() throws IOException {
+      stream.close();
+      jarFile.close();
+    }
+
+    @Override
+    public int read() throws IOException {
+      return stream.read();
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+      return stream.read(b);
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+      return stream.read(b, off, len);
+    }
+  }
+
+  private final JarEntry entry;
+  private final File file;
+
+  public JarEntryEmittedArtifact(String path, File jarFile, JarEntry entry) {
+    super(path);
+    this.file = jarFile;
+    this.entry = entry;
+  }
+
+  @Override
+  public InputStream getContents(TreeLogger logger)
+      throws UnableToCompleteException {
+    try {
+      return new JarEntryInputStream();
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "unexpected IOException", e);
+      throw new UnableToCompleteException();
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionInformation.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionInformation.java
new file mode 100644
index 0000000..f0980a6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionInformation.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2009 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.linker.Artifact;
+import com.google.gwt.core.ext.linker.Transferable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeMap;
+
+/**
+ * Used by {@link SelectionScriptLinker} to hold selection information about an
+ * individual compiled permutation. The linker generates one instance of this
+ * class per permutation and then accumulates them in the final link, where it
+ * generates a selection script.
+ */
+@Transferable
+public class SelectionInformation extends Artifact<SelectionInformation> {
+  private final int hashCode;
+  private final TreeMap<String, String> propMap;
+  private final String strongName;
+
+  public SelectionInformation(String strongName, TreeMap<String, String> propMap) {
+    super(SelectionScriptLinker.class);
+    this.strongName = strongName;
+    this.propMap = propMap;
+    hashCode = strongName.hashCode() + propMap.hashCode() * 17 + 11;
+  }
+
+  public TreeMap<String, String> getPropMap() {
+    return propMap;
+  }
+
+  public String getStrongName() {
+    return strongName;
+  }
+
+  @Override
+  public int hashCode() {
+    return hashCode;
+  }
+
+  @Override
+  protected int compareToComparableArtifact(SelectionInformation o) {
+    // compare the strong names
+    int cmp = getStrongName().compareTo(o.getStrongName());
+    if (cmp != 0) {
+      return cmp;
+    }
+
+    // compare the size of the property maps
+    if (getPropMap().size() != o.getPropMap().size()) {
+      return getPropMap().size() - o.getPropMap().size();
+    }
+
+    // compare the key sets of the property maps
+    List<String> myKeys = new ArrayList<String>(getPropMap().keySet());
+    List<String> oKeys = new ArrayList<String>(o.getPropMap().keySet());
+    for (int i = 0; i < myKeys.size(); i++) {
+      cmp = myKeys.get(i).compareTo(oKeys.get(i));
+      if (cmp != 0) {
+        return cmp;
+      }
+    }
+
+    // compare the property map values
+    for (String key : getPropMap().keySet()) {
+      cmp = getPropMap().get(key).compareTo(o.getPropMap().get(key));
+      if (cmp != 0) {
+        return cmp;
+      }
+    }
+
+    return 0;
+  }
+
+  @Override
+  protected Class<SelectionInformation> getComparableArtifactType() {
+    return SelectionInformation.class;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
index 5278b84..c5d332a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
@@ -19,13 +19,18 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.AbstractLinker;
+import com.google.gwt.core.ext.linker.Artifact;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.ScriptReference;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.StylesheetReference;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.StaticPropertyOracle;
 import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.collect.HashSet;
+import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.util.tools.Utility;
 
 import java.io.File;
@@ -34,10 +39,11 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
-import java.util.SortedSet;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 
 /**
  * A base class for Linkers that use an external script to boostrap the GWT
@@ -66,7 +72,6 @@
    * @param src the test url
    * @return <code>true</code> if the URL is relative, <code>false</code> if not
    */
-  @SuppressWarnings("unused")
   protected static boolean isRelativeURL(String src) {
     // A straight absolute url for the same domain, server, and protocol.
     if (src.startsWith("/")) {
@@ -97,22 +102,50 @@
     }
   }
 
-  private final Map<CompilationResult, String> compilationStrongNames = new IdentityHashMap<CompilationResult, String>();
+  /**
+   * This maps each compilation strong name to the property settings for that
+   * compilation. A single compilation can have multiple property settings if
+   * the compiles for those settings yielded the exact same compiled output.
+   */
+  private final SortedMap<String, List<Map<String, String>>> propMapsByStrongName = new TreeMap<String, List<Map<String, String>>>();
 
+  /**
+   * This method is left in place for existing subclasses of
+   * SelectionScriptLinker that have not been upgraded for the sharding API.
+   */
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
       ArtifactSet artifacts) throws UnableToCompleteException {
-    ArtifactSet toReturn = new ArtifactSet(artifacts);
-
-    for (CompilationResult compilation : toReturn.find(CompilationResult.class)) {
-      toReturn.addAll(doEmitCompilation(logger, context, compilation));
-    }
-
-    toReturn.add(emitSelectionScript(logger, context, artifacts));
+    ArtifactSet toReturn = link(logger, context, artifacts, true);
+    toReturn = link(logger, context, artifacts, false);
     return toReturn;
   }
 
-  protected Collection<EmittedArtifact> doEmitCompilation(TreeLogger logger,
+  @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts, boolean onePermutation)
+      throws UnableToCompleteException {
+    if (onePermutation) {
+      ArtifactSet toReturn = new ArtifactSet(artifacts);
+
+      /*
+       * Support having multiple compilation results because this method is also
+       * called from the legacy link method.
+       */
+      for (CompilationResult compilation : toReturn.find(CompilationResult.class)) {
+        toReturn.addAll(doEmitCompilation(logger, context, compilation));
+      }
+      return toReturn;
+    } else {
+      processSelectionInformation(artifacts);
+
+      ArtifactSet toReturn = new ArtifactSet(artifacts);
+      toReturn.add(emitSelectionScript(logger, context, artifacts));
+      return toReturn;
+    }
+  }
+
+  protected Collection<Artifact<?>> doEmitCompilation(TreeLogger logger,
       LinkerContext context, CompilationResult result)
       throws UnableToCompleteException {
     String[] js = result.getJavaScript();
@@ -122,7 +155,7 @@
       bytes[i] = Util.getBytes(js[i]);
     }
 
-    Collection<EmittedArtifact> toReturn = new ArrayList<EmittedArtifact>();
+    Collection<Artifact<?>> toReturn = new ArrayList<Artifact<?>>();
     toReturn.add(emitBytes(logger, bytes[0], result.getStrongName()
         + getCompilationExtension(logger, context)));
     for (int i = 1; i < js.length; i++) {
@@ -130,7 +163,8 @@
           + result.getStrongName() + File.separator + i + FRAGMENT_EXTENSION));
     }
 
-    compilationStrongNames.put(result, result.getStrongName());
+    toReturn.addAll(emitSelectionInformation(result.getPermutation().getId(),
+        result.getStrongName(), result.getPermutation().getPropertyOracles()));
 
     return toReturn;
   }
@@ -217,15 +251,17 @@
     }
   }
 
+  /**
+   * Generate a selection script. The selection information should previously
+   * have been scanned using {@link #processSelectionInformation(ArtifactSet)}.
+   */
   protected String generateSelectionScript(TreeLogger logger,
       LinkerContext context, ArtifactSet artifacts)
       throws UnableToCompleteException {
-
     StringBuffer selectionScript;
     try {
       selectionScript = new StringBuffer(
-          Utility.getFileFromClassPath(getSelectionScriptTemplate(logger,
-              context)));
+          Utility.getFileFromClassPath(getSelectionScriptTemplate(logger, context)));
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Unable to read selection script template",
           e);
@@ -268,31 +304,30 @@
     }
 
     // Possibly add permutations
-    SortedSet<CompilationResult> compilations = artifacts.find(CompilationResult.class);
     startPos = selectionScript.indexOf("// __PERMUTATIONS_END__");
     if (startPos != -1) {
       StringBuffer text = new StringBuffer();
-      if (compilations.size() == 0) {
+      if (propMapsByStrongName.size() == 0) {
         // Hosted mode link.
-        text.append("alert(\"GWT module '"
-            + context.getModuleName()
+        text.append("alert(\"GWT module '" + context.getModuleName()
             + "' may need to be (re)compiled\");");
         text.append("return;");
 
-      } else if (compilations.size() == 1) {
+      } else if (propMapsByStrongName.size() == 1) {
         // Just one distinct compilation; no need to evaluate properties
-        Iterator<CompilationResult> iter = compilations.iterator();
-        CompilationResult result = iter.next();
-        text.append("strongName = '" + compilationStrongNames.get(result)
-            + "';");
+        text.append("strongName = '"
+            + propMapsByStrongName.keySet().iterator().next() + "';");
       } else {
-        for (CompilationResult r : compilations) {
-          for (Map<SelectionProperty, String> propertyMap : r.getPropertyMap()) {
+        Set<String> propertiesUsed = new HashSet<String>();
+        for (String strongName : propMapsByStrongName.keySet()) {
+          for (Map<String, String> propertyMap : propMapsByStrongName.get(strongName)) {
             // unflatten([v1, v2, v3], 'strongName');
             text.append("unflattenKeylistIntoAnswers([");
             boolean needsComma = false;
             for (SelectionProperty p : context.getProperties()) {
-              if (!propertyMap.containsKey(p)) {
+              if (p.tryGetValue() != null) {
+                continue;
+              } else if (p.isDerived()) {
                 continue;
               }
 
@@ -301,10 +336,10 @@
               } else {
                 needsComma = true;
               }
-              text.append("'" + propertyMap.get(p) + "'");
+              text.append("'" + propertyMap.get(p.getName()) + "'");
+              propertiesUsed.add(p.getName());
             }
-            text.append("], '").append(compilationStrongNames.get(r)).append(
-                "');\n");
+            text.append("], '").append(strongName).append("');\n");
           }
         }
 
@@ -312,9 +347,7 @@
         text.append("strongName = answers[");
         boolean needsIndexMarkers = false;
         for (SelectionProperty p : context.getProperties()) {
-          if (p.tryGetValue() != null) {
-            continue;
-          } else if (p.isDerived()) {
+          if (!propertiesUsed.contains(p.getName())) {
             continue;
           }
           if (needsIndexMarkers) {
@@ -364,16 +397,6 @@
       LinkerContext context) throws UnableToCompleteException;
 
   /**
-   * Get the partial path on which a CompilationResult has been emitted.
-   * 
-   * @return the partial path, or <code>null</code> if the CompilationResult has
-   *         not been emitted.
-   */
-  protected String getCompilationStrongName(CompilationResult result) {
-    return compilationStrongNames.get(result);
-  }
-
-  /**
    * Compute the beginning of a JavaScript file that will hold the main module
    * implementation.
    */
@@ -398,6 +421,49 @@
   protected abstract String getModuleSuffix(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException;
 
-  protected abstract String getSelectionScriptTemplate(TreeLogger logger,
-      LinkerContext context) throws UnableToCompleteException;
+  protected abstract String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context)
+      throws UnableToCompleteException;
+
+  /**
+   * Find all instances of {@link SelectionInformation} and add them to the
+   * internal map of selection information.
+   */
+  protected void processSelectionInformation(ArtifactSet artifacts) {
+    for (SelectionInformation selInfo : artifacts.find(SelectionInformation.class)) {
+      processSelectionInformation(selInfo);
+    }
+  }
+
+  private List<Artifact<?>> emitSelectionInformation(int id, String strongName,
+      StaticPropertyOracle[] staticPropertyOracles) {
+    List<Artifact<?>> emitted = new ArrayList<Artifact<?>>();
+
+    for (int propOracleId = 0; propOracleId < staticPropertyOracles.length; propOracleId++) {
+      StaticPropertyOracle propOracle = staticPropertyOracles[propOracleId];
+      TreeMap<String, String> propMap = new TreeMap<String, String>();
+
+      BindingProperty[] orderedProps = propOracle.getOrderedProps();
+      String[] orderedPropValues = propOracle.getOrderedPropValues();
+      for (int i = 0; i < orderedProps.length; i++) {
+        propMap.put(orderedProps[i].getName(), orderedPropValues[i]);
+      }
+      emitted.add(new SelectionInformation(strongName, propMap));
+    }
+
+    return emitted;
+  }
+
+  private Map<String, String> processSelectionInformation(
+      SelectionInformation selInfo) {
+    TreeMap<String, String> entries = selInfo.getPropMap();
+    String strongName = selInfo.getStrongName();
+    if (!propMapsByStrongName.containsKey(strongName)) {
+      propMapsByStrongName.put(strongName,
+          Lists.<Map<String, String>> create(entries));
+    } else {
+      propMapsByStrongName.put(strongName, Lists.add(
+          propMapsByStrongName.get(strongName), entries));
+    }
+    return entries;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
index 131eed6..b80f8bb 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
@@ -19,7 +19,10 @@
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.StatementRanges;
 import com.google.gwt.core.ext.linker.SymbolData;
+import com.google.gwt.dev.Permutation;
+import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.util.DiskCache;
+import com.google.gwt.dev.util.Util;
 
 import java.io.Serializable;
 import java.util.Collections;
@@ -71,8 +74,6 @@
   private static final DiskCache diskCache = new DiskCache();
 
   private final long jsToken[];
-  
-  private final int permutationId;
 
   private final SortedSet<SortedMap<SelectionProperty, String>> propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>(
       MAP_COMPARATOR);
@@ -83,17 +84,18 @@
 
   private final long symbolToken;
 
-  public StandardCompilationResult(String strongName, byte[][] js,
-      byte[] serializedSymbolMap, StatementRanges[] statementRanges, int permutationId) {
-    super(StandardLinkerContext.class);
-    this.strongName = strongName;
+  public StandardCompilationResult(PermutationResult permutationResult) {
+    super(StandardLinkerContext.class, permutationResult.getPermutation());
+    byte[][] js = permutationResult.getJs();
+    strongName = Util.computeStrongName(js);
+    byte[] serializedSymbolMap = permutationResult.getSerializedSymbolMap();
+    statementRanges = permutationResult.getStatementRanges();
+    Permutation permutation = permutationResult.getPermutation();
     jsToken = new long[js.length];
     for (int i = 0; i < jsToken.length; ++i) {
       jsToken[i] = diskCache.writeByteArray(js[i]);
     }
     symbolToken = diskCache.writeByteArray(serializedSymbolMap);
-    this.statementRanges = statementRanges;
-    this.permutationId = permutationId;
   }
 
   /**
@@ -117,11 +119,6 @@
   }
 
   @Override
-  public int getPermutationId() {
-    return permutationId;
-  }
-  
-  @Override
   public SortedSet<SortedMap<SelectionProperty, String>> getPropertyMap() {
     return Collections.unmodifiableSortedSet(propertyValues);
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 52c7ccc..16c8091 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -32,7 +32,6 @@
 import com.google.gwt.dev.cfg.Script;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.JJSOptions;
-import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.js.JsObfuscateNamer;
 import com.google.gwt.dev.js.JsParser;
@@ -52,7 +51,6 @@
 import com.google.gwt.dev.js.ast.JsScope;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.OutputFileSet;
-import com.google.gwt.dev.util.Util;
 
 import java.io.File;
 import java.io.IOException;
@@ -111,21 +109,20 @@
     }
   };
 
-  private final ArtifactSet artifacts = new ArtifactSet();
-
   private final SortedSet<ConfigurationProperty> configurationProperties;
-  private final JJSOptions jjsOptions;
-  private final Map<Class<? extends Linker>, String> linkerShortNames = new HashMap<Class<? extends Linker>, String>();
 
+  private final JJSOptions jjsOptions;
+
+  private final List<Class<? extends Linker>> linkerClasses;
+  private Linker[] linkers;
+  private final Map<Class<? extends Linker>, String> linkerShortNames = new HashMap<Class<? extends Linker>, String>();
   private final String moduleFunctionName;
   private final long moduleLastModified;
   private final String moduleName;
 
   private final Map<String, StandardSelectionProperty> propertiesByName = new HashMap<String, StandardSelectionProperty>();
-  private final Map<String, StandardCompilationResult> resultsByStrongName = new HashMap<String, StandardCompilationResult>();
-  private final SortedSet<SelectionProperty> selectionProperties;
 
-  private final Linker[] linkers;
+  private final SortedSet<SelectionProperty> selectionProperties;
 
   public StandardLinkerContext(TreeLogger logger, ModuleDef module,
       JJSOptions jjsOptions) throws UnableToCompleteException {
@@ -138,24 +135,26 @@
     this.moduleLastModified = module.lastModified();
 
     // Sort the linkers into the order they should actually run.
-    List<Class<? extends Linker>> sortedLinkers = new ArrayList<Class<? extends Linker>>();
+    linkerClasses = new ArrayList<Class<? extends Linker>>();
 
     // Get all the pre-linkers first.
     for (Class<? extends Linker> linkerClass : module.getActiveLinkers()) {
       Order order = linkerClass.getAnnotation(LinkerOrder.class).value();
       assert (order != null);
       if (order == Order.PRE) {
-        sortedLinkers.add(linkerClass);
+        linkerClasses.add(linkerClass);
       }
     }
 
     // Get the primary linker.
     Class<? extends Linker> primary = module.getActivePrimaryLinker();
     if (primary == null) {
-      logger.log(logger.ERROR, "Primary linker is null.  Does your module " +
-          "inherit from com.google.gwt.core.Core or com.google.gwt.user.User?");
+      logger.log(
+          TreeLogger.ERROR,
+          "Primary linker is null.  Does your module "
+              + "inherit from com.google.gwt.core.Core or com.google.gwt.user.User?");
     } else {
-      sortedLinkers.add(module.getActivePrimaryLinker());
+      linkerClasses.add(module.getActivePrimaryLinker());
     }
 
     // Get all the post-linkers IN REVERSE ORDER.
@@ -169,22 +168,10 @@
         }
       }
       Collections.reverse(postLinkerClasses);
-      sortedLinkers.addAll(postLinkerClasses);
+      linkerClasses.addAll(postLinkerClasses);
     }
 
-    linkers = new Linker[sortedLinkers.size()];
-    int i = 0;
-    for (Class<? extends Linker> linkerClass : sortedLinkers) {
-      try {
-        linkers[i++] = linkerClass.newInstance();
-      } catch (InstantiationException e) {
-        logger.log(TreeLogger.ERROR, "Unable to create Linker", e);
-        throw new UnableToCompleteException();
-      } catch (IllegalAccessException e) {
-        logger.log(TreeLogger.ERROR, "Unable to create Linker", e);
-        throw new UnableToCompleteException();
-      }
-    }
+    resetLinkers(logger);
 
     for (Map.Entry<String, Class<? extends Linker>> entry : module.getLinkers().entrySet()) {
       linkerShortNames.put(entry.getValue(), entry.getKey());
@@ -225,10 +212,33 @@
       selectionProperties = Collections.unmodifiableSortedSet(mutableSelectionProperties);
       configurationProperties = Collections.unmodifiableSortedSet(mutableConfigurationProperties);
     }
+  }
 
-    /*
-     * Add static resources in the specified module as artifacts.
-     */
+  public boolean allLinkersAreShardable() {
+    return findUnshardableLinkers().isEmpty();
+  }
+
+  /**
+   * Find all linkers that are not updated to support running generators on
+   * compilations shards.
+   */
+  public List<Linker> findUnshardableLinkers() {
+    List<Linker> problemLinkers = new ArrayList<Linker>();
+
+    for (Linker linker : linkers) {
+      if (!linker.isShardable()) {
+        problemLinkers.add(linker);
+      }
+    }
+    return problemLinkers;
+  }
+
+  /**
+   * Convert all static resources in the specified module to artifacts.
+   */
+  public ArtifactSet getArtifactsForPublicResources(TreeLogger logger,
+      ModuleDef module) {
+    ArtifactSet artifacts = new ArtifactSet();
     for (String path : module.getAllPublicFiles()) {
       String partialPath = path.replace(File.separatorChar, '/');
       PublicResource resource = new StandardPublicResource(partialPath,
@@ -237,43 +247,23 @@
       logger.log(TreeLogger.SPAM, "Added public resource " + resource, null);
     }
 
-    recordStaticReferences(logger, module);
-  }
-
-  /**
-   * Adds or replaces Artifacts in the ArtifactSet that will be passed into the
-   * Linkers invoked.
-   */
-  public void addOrReplaceArtifacts(ArtifactSet artifacts) {
-    this.artifacts.removeAll(artifacts);
-    this.artifacts.addAll(artifacts);
-  }
-
-  /**
-   * Returns the ArtifactSet that will passed into the invoke Linkers.
-   */
-  public ArtifactSet getArtifacts() {
-    return artifacts;
-  }
-
-  /**
-   * Gets or creates a CompilationResult for the given JavaScript program.
-   */
-  public StandardCompilationResult getCompilation(
-      PermutationResult permutationResult) {
-    byte[][] js = permutationResult.getJs();
-    String strongName = Util.computeStrongName(js);
-    StandardCompilationResult result = resultsByStrongName.get(strongName);
-    if (result == null) {
-      result = new StandardCompilationResult(strongName, js,
-          permutationResult.getSerializedSymbolMap(),
-          permutationResult.getStatementRanges(),
-          permutationResult.getPermutation().getId());
-      resultsByStrongName.put(result.getStrongName(), result);
-      artifacts.add(result);
+    {
+      int index = 0;
+      for (Script script : module.getScripts()) {
+        String url = script.getSrc();
+        artifacts.add(new StandardScriptReference(url, index++));
+        logger.log(TreeLogger.SPAM, "Added script " + url, null);
+      }
     }
-    artifacts.addAll(permutationResult.getArtifacts());
-    return result;
+
+    {
+      int index = 0;
+      for (String style : module.getStyles()) {
+        artifacts.add(new StandardStylesheetReference(style, index++));
+        logger.log(TreeLogger.SPAM, "Added style " + style, null);
+      }
+    }
+    return artifacts;
   }
 
   public SortedSet<ConfigurationProperty> getConfigurationProperties() {
@@ -285,6 +275,19 @@
     return "Root Linker";
   }
 
+  /**
+   * Return the full path for an artifact produced by <code>linkertype</code>
+   * that has the specified partial path. The full path will be the linker's
+   * short name, as defined in the module file, followed by the given partial
+   * path.
+   */
+  public String getExtraPathForLinker(Class<? extends Linker> linkerType,
+      String partialPath) {
+    assert linkerShortNames.containsKey(linkerType) : linkerType.getName()
+        + " unknown";
+    return linkerShortNames.get(linkerType) + '/' + partialPath;
+  }
+
   public String getModuleFunctionName() {
     return moduleFunctionName;
   }
@@ -305,27 +308,75 @@
     return propertiesByName.get(name);
   }
 
-  /**
-   * Run the linker stack.
-   */
-  public ArtifactSet invokeLink(TreeLogger logger)
+  public ArtifactSet invokeFinalLink(TreeLogger logger, ArtifactSet artifacts)
       throws UnableToCompleteException {
+    for (Linker linker : linkers) {
+      if (linker.isShardable()) {
+        TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE,
+            "Invoking Linker " + linker.getDescription(), null);
+        artifacts = linker.link(linkerLogger, this, artifacts, false);
+      }
+    }
+    return artifacts;
+  }
+
+  /**
+   * Run linkers that have not been updated for the shardable API.
+   */
+  public ArtifactSet invokeLegacyLinkers(TreeLogger logger,
+      ArtifactSet artifacts) throws UnableToCompleteException {
     ArtifactSet workingArtifacts = new ArtifactSet(artifacts);
 
     for (Linker linker : linkers) {
-      TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE,
-          "Invoking Linker " + linker.getDescription(), null);
-      workingArtifacts.freeze();
-      try {
-        workingArtifacts = linker.link(linkerLogger, this, workingArtifacts);
-      } catch (Throwable e) {
-        linkerLogger.log(TreeLogger.ERROR, "Failed to link", e);
-        throw new UnableToCompleteException();
+      if (!linker.isShardable()) {
+        TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE,
+            "Invoking Linker " + linker.getDescription(), null);
+        workingArtifacts.freeze();
+        try {
+          workingArtifacts = linker.link(linkerLogger, this, workingArtifacts);
+        } catch (Throwable e) {
+          linkerLogger.log(TreeLogger.ERROR, "Failed to link", e);
+          throw new UnableToCompleteException();
+        }
       }
     }
     return workingArtifacts;
   }
 
+  /**
+   * Invoke the shardable linkers on one permutation result. Those linkers run
+   * with the precompile artifacts as input.
+   */
+  public ArtifactSet invokeLinkForOnePermutation(TreeLogger logger,
+      StandardCompilationResult permResult, ArtifactSet permArtifacts)
+      throws UnableToCompleteException {
+    ArtifactSet workingArtifacts = new ArtifactSet(permArtifacts);
+    workingArtifacts.add(permResult);
+
+    for (Linker linker : linkers) {
+      if (linker.isShardable()) {
+        TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE,
+            "Invoking Linker " + linker.getDescription(), null);
+        try {
+          workingArtifacts.freeze();
+          workingArtifacts = linker.link(logger, this, workingArtifacts, true);
+        } catch (Throwable e) {
+          linkerLogger.log(TreeLogger.ERROR, "Failed to link", e);
+          throw new UnableToCompleteException();
+        }
+      }
+    }
+
+    /*
+     * Reset linkers so that they don't accidentally carry any state across
+     * permutations
+     */
+    resetLinkers(logger);
+
+    workingArtifacts.freeze();
+    return workingArtifacts;
+  }
+
   public ArtifactSet invokeRelink(TreeLogger logger,
       ArtifactSet newlyGeneratedArtifacts) throws UnableToCompleteException {
     ArtifactSet workingArtifacts = new ArtifactSet(newlyGeneratedArtifacts);
@@ -350,7 +401,7 @@
 
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException {
+      ArtifactSet artifacts) {
     throw new UnsupportedOperationException();
   }
 
@@ -455,35 +506,20 @@
   }
 
   /**
-   * Creates a linker-specific subdirectory in the module's auxiliary output
-   * directory.
+   * (Re)instantiate all linkers.
    */
-  private String getExtraPathForLinker(Class<? extends Linker> linkerType,
-      String partialPath) {
-    assert linkerShortNames.containsKey(linkerType) : linkerType.getName()
-        + " unknown";
-    return linkerShortNames.get(linkerType) + '/' + partialPath;
-  }
-
-  /**
-   * Record script references and CSS references that are listed in the module
-   * file.
-   */
-  private void recordStaticReferences(TreeLogger logger, ModuleDef module) {
-    {
-      int index = 0;
-      for (Script script : module.getScripts()) {
-        String url = script.getSrc();
-        artifacts.add(new StandardScriptReference(url, index++));
-        logger.log(TreeLogger.SPAM, "Added script " + url, null);
-      }
-    }
-
-    {
-      int index = 0;
-      for (String style : module.getStyles()) {
-        artifacts.add(new StandardStylesheetReference(style, index++));
-        logger.log(TreeLogger.SPAM, "Added style " + style, null);
+  private void resetLinkers(TreeLogger logger) throws UnableToCompleteException {
+    linkers = new Linker[linkerClasses.size()];
+    int i = 0;
+    for (Class<? extends Linker> linkerClass : linkerClasses) {
+      try {
+        linkers[i++] = linkerClass.newInstance();
+      } catch (InstantiationException e) {
+        logger.log(TreeLogger.ERROR, "Unable to create Linker", e);
+        throw new UnableToCompleteException();
+      } catch (IllegalAccessException e) {
+        logger.log(TreeLogger.ERROR, "Unable to create Linker", e);
+        throw new UnableToCompleteException();
       }
     }
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java
index 456d381..b25c841 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java
@@ -16,10 +16,12 @@
 package com.google.gwt.core.ext.linker.impl;
 
 import com.google.gwt.core.ext.linker.ScriptReference;
+import com.google.gwt.core.ext.linker.Transferable;
 
 /**
  * The standard implementation of {@link ScriptReference}.
  */
+@Transferable
 public class StandardScriptReference extends ScriptReference {
 
   public StandardScriptReference(String src, int index) {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java
index 3534663..e2e7f53 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java
@@ -16,10 +16,12 @@
 package com.google.gwt.core.ext.linker.impl;
 
 import com.google.gwt.core.ext.linker.StylesheetReference;
+import com.google.gwt.core.ext.linker.Transferable;
 
 /**
  * The standard implementation of {@link StylesheetReference}.
  */
+@Transferable
 public class StandardStylesheetReference extends StylesheetReference {
 
   public StandardStylesheetReference(String src, int index) {
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
index 5300b5e..e0c972b 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -23,6 +23,7 @@
 import com.google.gwt.core.ext.linker.ConfigurationProperty;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.StatementRanges;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.core.ext.linker.impl.HostedModeLinker;
@@ -42,6 +43,7 @@
  * a separate iframe.
  */
 @LinkerOrder(Order.PRIMARY)
+@Shardable
 public class IFrameLinker extends SelectionScriptLinker {
   /**
    * This string is inserted between script chunks. It is made default access
@@ -111,8 +113,12 @@
 
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException {
-    ArtifactSet toReturn = super.link(logger, context, artifacts);
+      ArtifactSet artifacts, boolean onePerm) throws UnableToCompleteException {
+    ArtifactSet toReturn = super.link(logger, context, artifacts, onePerm);
+
+    if (onePerm) {
+      return toReturn;
+    }
 
     try {
       // Add hosted mode iframe contents
@@ -153,7 +159,7 @@
     return toReturn;
   }
 
-  /**
+  /*
    * This implementation divides the code of the initial fragment into multiple
    * script tags. These chunked script tags loads faster on Firefox even when
    * the data is cached. Additionally, having the script tags separated means
@@ -221,8 +227,7 @@
   }
 
   @Override
-  protected String getSelectionScriptTemplate(TreeLogger logger,
-      LinkerContext context) {
+  protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context) {
     return "com/google/gwt/core/linker/IFrameTemplate.js";
   }
 
diff --git a/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
index 9b2769b..140d90c 100644
--- a/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
@@ -18,10 +18,12 @@
 import com.google.gwt.core.ext.LinkerContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.Artifact;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.core.ext.linker.impl.SelectionScriptLinker;
 import com.google.gwt.dev.About;
@@ -36,6 +38,7 @@
  * result.
  */
 @LinkerOrder(Order.PRIMARY)
+@Shardable
 public class SingleScriptLinker extends SelectionScriptLinker {
   @Override
   public String getDescription() {
@@ -44,16 +47,20 @@
 
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException {
-    ArtifactSet toReturn = new ArtifactSet(artifacts);
-
-    toReturn.add(emitSelectionScript(logger, context, artifacts));
-
-    return toReturn;
+      ArtifactSet artifacts, boolean onePermutation)
+      throws UnableToCompleteException {
+    if (onePermutation) {
+      processSelectionInformation(artifacts);
+      ArtifactSet toReturn = new ArtifactSet(artifacts);
+      toReturn.add(emitSelectionScript(logger, context, artifacts));
+      return toReturn;
+    } else {
+      return artifacts;
+    }
   }
 
   @Override
-  protected Collection<EmittedArtifact> doEmitCompilation(TreeLogger logger,
+  protected Collection<Artifact<?>> doEmitCompilation(TreeLogger logger,
       LinkerContext context, CompilationResult result)
       throws UnableToCompleteException {
     if (result.getJavaScript().length != 1) {
@@ -71,7 +78,7 @@
       throws UnableToCompleteException {
 
     DefaultTextOutput out = new DefaultTextOutput(true);
-
+    
     // Emit the selection script.
     String bootstrap = generateSelectionScript(logger, context, artifacts);
     bootstrap = context.optimizeJavaScript(logger, bootstrap);
@@ -95,9 +102,8 @@
     // Find the single CompilationResult
     Set<CompilationResult> results = artifacts.find(CompilationResult.class);
     if (results.size() != 1) {
-      logger.log(TreeLogger.ERROR,
-          "The module must have exactly one distinct"
-              + " permutation when using the " + getDescription() + " Linker.",
+      logger.log(TreeLogger.ERROR, "The module must have exactly one distinct"
+          + " permutation when using the " + getDescription() + " Linker.",
           null);
       throw new UnableToCompleteException();
     }
@@ -158,8 +164,8 @@
   }
 
   @Override
-  protected String getSelectionScriptTemplate(TreeLogger logger,
-      LinkerContext context) throws UnableToCompleteException {
+  protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context)
+      throws UnableToCompleteException {
     return "com/google/gwt/core/linker/SingleScriptTemplate.js";
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java b/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
index c2216f3..7b85d26 100644
--- a/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
@@ -18,12 +18,14 @@
 import com.google.gwt.core.ext.Linker;
 import com.google.gwt.core.ext.LinkerContext;
 import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.Artifact;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
+import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.SelectionProperty;
-import com.google.gwt.core.ext.linker.SyntheticArtifact;
+import com.google.gwt.core.ext.linker.Shardable;
+import com.google.gwt.core.ext.linker.Transferable;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.soyc.SoycDashboard;
 import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
@@ -32,15 +34,72 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.SortedMap;
-import java.util.SortedSet;
 import java.util.TreeMap;
 
 /**
- * Generates the top-level report files for a compile report.
+ * Converts SOYC report files into emitted private artifacts.
  */
 @LinkerOrder(Order.POST)
+@Shardable
 public class SoycReportLinker extends Linker {
+  /**
+   * An artifact giving a one-line description of a permutation ID in terms of
+   * its deferred bindings.
+   */
+  @Transferable
+  private static class PermDescriptionArtifact extends
+      Artifact<PermDescriptionArtifact> {
+
+    private List<String> permDesc;
+    private int permId;
+
+    public PermDescriptionArtifact(int permId, List<String> permDesc) {
+      super(SoycReportLinker.class);
+      this.permId = permId;
+      this.permDesc = permDesc;
+    }
+
+    public List<String> getPermDesc() {
+      return permDesc;
+    }
+
+    public int getPermId() {
+      return permId;
+    }
+
+    @Override
+    public int hashCode() {
+      return permId;
+    }
+
+    @Override
+    protected int compareToComparableArtifact(PermDescriptionArtifact o) {
+      int cmp;
+      cmp = permId - o.getPermId();
+      if (cmp != 0) {
+        return cmp;
+      }
+
+      cmp = permDesc.size() - o.getPermDesc().size();
+      if (cmp != 0) {
+        return cmp;
+      }
+
+      for (int i = 0; i < permDesc.size(); i++) {
+        cmp = permDesc.get(i).compareTo(o.getPermDesc().get(i));
+        if (cmp != 0) {
+          return cmp;
+        }
+      }
+
+      return 0;
+    }
+
+    @Override
+    protected Class<PermDescriptionArtifact> getComparableArtifactType() {
+      return PermDescriptionArtifact.class;
+    }
+  }
 
   @Override
   public String getDescription() {
@@ -49,51 +108,70 @@
 
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException {
-    if (!includesReports(artifacts)) {
+      ArtifactSet artifacts, boolean onePermutation) {
+    if (!anyReportFilesPresent(artifacts)) {
+      // No report was generated
       return artifacts;
     }
 
-    ArtifactSet results = new ArtifactSet(artifacts);
+    if (onePermutation) {
+      return emitPermutationDescriptions(logger, context, artifacts);
+    } else {
+      return buildTopLevelFiles(logger, context, artifacts);
+    }
+  }
 
-    // Run the final step of the dashboard to generate top-level files.
+  private boolean anyReportFilesPresent(ArtifactSet artifacts) {
+    String prefix = "soycReport/"
+        + ArtifactsOutputDirectory.COMPILE_REPORT_DIRECTORY + "/";
+    for (EmittedArtifact art : artifacts.find(EmittedArtifact.class)) {
+      if (art.getPartialPath().startsWith(prefix)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private ArtifactSet buildTopLevelFiles(TreeLogger logger,
+      LinkerContext context, ArtifactSet artifacts) {
+    artifacts = new ArtifactSet(artifacts);
+
     ArtifactsOutputDirectory out = new ArtifactsOutputDirectory();
     try {
       new SoycDashboard(out).generateCrossPermutationFiles(extractPermutationDescriptions(artifacts));
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR,
           "Error while generating a Story of Your Compile", e);
+      e.printStackTrace();
     }
-    results.addAll(out.getArtifacts());
 
-    return results;
+    artifacts.addAll(out.getArtifacts());
+    return artifacts;
+  }
+
+  private ArtifactSet emitPermutationDescriptions(TreeLogger logger,
+      LinkerContext context, ArtifactSet artifacts) {
+    artifacts = new ArtifactSet(artifacts);
+
+    for (CompilationResult res : artifacts.find(CompilationResult.class)) {
+      int permId = res.getPermutationId();
+      List<String> permDesc = new ArrayList<String>();
+      for (Map<SelectionProperty, String> propertyMap : res.getPropertyMap()) {
+        permDesc.add(SymbolMapsLinker.propertyMapToString(propertyMap));
+      }
+
+      artifacts.add(new PermDescriptionArtifact(permId, permDesc));
+    }
+
+    return artifacts;
   }
 
   private Map<String, List<String>> extractPermutationDescriptions(
       ArtifactSet artifacts) {
-    Map<String, List<String>> permutationDescriptions = new TreeMap<String, List<String>>();
-
-    for (CompilationResult res : artifacts.find(CompilationResult.class)) {
-      String permId = Integer.toString(res.getPermutationId());
-      List<String> permDescList = new ArrayList<String>();
-      SortedSet<SortedMap<SelectionProperty, String>> allPropertiesMap = res.getPropertyMap();
-      for (SortedMap<SelectionProperty, String> propertyMap : allPropertiesMap) {
-         String permDesc = SymbolMapsLinker.propertyMapToString(propertyMap);
-         permDescList.add(permDesc);
-      }
-      permutationDescriptions.put(permId, permDescList);
+    Map<String, List<String>> permDescriptions = new TreeMap<String, List<String>>();
+    for (PermDescriptionArtifact art : artifacts.find(PermDescriptionArtifact.class)) {
+      permDescriptions.put(Integer.toString(art.getPermId()), art.getPermDesc());
     }
-
-    return permutationDescriptions;
-  }
-
-  private boolean includesReports(ArtifactSet artifacts) {
-    for (SyntheticArtifact art : artifacts.find(SyntheticArtifact.class)) {
-      if (art.getPartialPath().startsWith(
-          ArtifactsOutputDirectory.COMPILE_REPORT_DIRECTORY + "/")) {
-        return true;
-      }
-    }
-    return false;
+    return permDescriptions;
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
index 298b9f8..321c29e 100644
--- a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
@@ -24,6 +24,7 @@
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 
@@ -40,6 +41,7 @@
  * {@link CompilationResult#getStrongName()}.
  */
 @LinkerOrder(Order.POST)
+@Shardable
 public class SymbolMapsLinker extends AbstractLinker {
 
   /**
@@ -80,23 +82,33 @@
     return "Export CompilationResult symbol maps";
   }
 
+  /**
+   * Included to support legacy non-shardable subclasses.
+   */
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
       ArtifactSet artifacts) throws UnableToCompleteException {
+    return link(logger, context, artifacts, true);
+  }
 
-    artifacts = new ArtifactSet(artifacts);
+  @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts, boolean onePermutation)
+      throws UnableToCompleteException {
+    if (onePermutation) {
+      artifacts = new ArtifactSet(artifacts);
 
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
-    for (CompilationResult result : artifacts.find(CompilationResult.class)) {
-      PrintWriter pw = new PrintWriter(out);
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      for (CompilationResult result : artifacts.find(CompilationResult.class)) {
+        PrintWriter pw = new PrintWriter(out);
 
-      doWriteSymbolMap(logger, result, pw);
-      pw.close();
+        doWriteSymbolMap(logger, result, pw);
+        pw.close();
 
-      doEmitSymbolMap(logger, artifacts, result, out);
-      out.reset();
+        doEmitSymbolMap(logger, artifacts, result, out);
+        out.reset();
+      }
     }
-
     return artifacts;
   }
 
diff --git a/dev/core/src/com/google/gwt/core/linker/XSLinker.java b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
index bdff4f7..6dbedba 100644
--- a/dev/core/src/com/google/gwt/core/linker/XSLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
@@ -18,9 +18,10 @@
 import com.google.gwt.core.ext.LinkerContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.Artifact;
 import com.google.gwt.core.ext.linker.CompilationResult;
-import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.core.ext.linker.impl.SelectionScriptLinker;
 import com.google.gwt.dev.About;
@@ -32,6 +33,7 @@
  * Generates a cross-site compatible bootstrap sequence.
  */
 @LinkerOrder(Order.PRIMARY)
+@Shardable
 public class XSLinker extends SelectionScriptLinker {
   @Override
   public String getDescription() {
@@ -39,7 +41,7 @@
   }
 
   @Override
-  protected Collection<EmittedArtifact> doEmitCompilation(TreeLogger logger,
+  protected Collection<Artifact<?>> doEmitCompilation(TreeLogger logger,
       LinkerContext context, CompilationResult result)
       throws UnableToCompleteException {
     if (result.getJavaScript().length != 1) {
@@ -112,8 +114,8 @@
   }
 
   @Override
-  protected String getSelectionScriptTemplate(TreeLogger logger,
-      LinkerContext context) throws UnableToCompleteException {
+  protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context)
+      throws UnableToCompleteException {
     return "com/google/gwt/core/linker/XSTemplate.js";
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index bf8815e..cc1019e 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -300,11 +300,6 @@
         return false;
       }
 
-      /*
-       * TODO(spoon) Check that all requested permutations have a permutation
-       * available before starting. probably needs two branches.
-       */
-
       if (precompileResults instanceof PrecompileOptions) {
         PrecompileOptions precompilationOptions = (PrecompileOptions) precompileResults;
         if (!precompileAndCompile(logger, moduleName, compilerWorkDir,
@@ -374,10 +369,9 @@
 
       PermutationResult permResult = compile(logger, subPerms[0],
           precompilation.getUnifiedAst());
-      FileBackedObject<PermutationResult> resultFile = new FileBackedObject<PermutationResult>(
-          PermutationResult.class, makePermFilename(compilerWorkDir, permId));
-      permResult.addArtifacts(precompilation.getGeneratedArtifacts());
-      resultFile.set(logger, permResult);
+      Link.linkOnePermutationToJar(logger, module,
+          precompilation.getGeneratedArtifacts(), permResult, makePermFilename(
+              compilerWorkDir, permId), precompilationOptions);
     }
 
     logger.log(TreeLogger.INFO, "Compile of permutations succeeded");
diff --git a/dev/core/src/com/google/gwt/dev/CompilePermsServer.java b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
index 012aac7..e119a8e 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
@@ -296,8 +296,8 @@
 
     Throwable caught = null;
     try {
-      logger.log(TreeLogger.DEBUG, "Compiling");
-      PermutationResult result = CompilePerms.compile(logger, p, ast);
+      PermutationResult result = CompilePerms.compile(logger.branch(
+          TreeLogger.DEBUG, "Compiling"), p, ast);
       resultFile.set(logger, result);
       logger.log(TreeLogger.DEBUG, "Successfully compiled permutation");
     } catch (UnableToCompleteException e) {
diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java
index c946cab..b3dabb2 100644
--- a/dev/core/src/com/google/gwt/dev/Compiler.java
+++ b/dev/core/src/com/google/gwt/dev/Compiler.java
@@ -218,12 +218,13 @@
 
           String logMessage = "Linking into " + absPath;
           if (options.getExtraDir() != null) {
-              File absExtrasPath = new File(options.getExtraDir(), module.getName());
-              absExtrasPath = absExtrasPath.getAbsoluteFile();
-              logMessage += "; Writing extras to " + absExtrasPath;
+            File absExtrasPath = new File(options.getExtraDir(),
+                module.getName());
+            absExtrasPath = absExtrasPath.getAbsoluteFile();
+            logMessage += "; Writing extras to " + absExtrasPath;
           }
-          Link.link(logger.branch(TreeLogger.TRACE, logMessage),
-              module, generatedArtifacts, resultFiles, options.getWarDir(),
+          Link.link(logger.branch(TreeLogger.TRACE, logMessage), module,
+              generatedArtifacts, allPerms, resultFiles, options.getWarDir(),
               options.getExtraDir(), precompileOptions);
 
           long compileDone = System.currentTimeMillis();
diff --git a/dev/core/src/com/google/gwt/dev/DevMode.java b/dev/core/src/com/google/gwt/dev/DevMode.java
index 4051228..f348824 100644
--- a/dev/core/src/com/google/gwt/dev/DevMode.java
+++ b/dev/core/src/com/google/gwt/dev/DevMode.java
@@ -26,6 +26,8 @@
 import com.google.gwt.dev.ui.RestartServerCallback;
 import com.google.gwt.dev.ui.RestartServerEvent;
 import com.google.gwt.dev.util.InstalledHelpInfo;
+import com.google.gwt.dev.util.NullOutputFileSet;
+import com.google.gwt.dev.util.OutputFileSet;
 import com.google.gwt.dev.util.OutputFileSetOnDirectory;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
@@ -386,8 +388,8 @@
     // Create the war directory if it doesn't exist
     File warDir = options.getWarDir();
     if (!warDir.exists() && !warDir.mkdirs()) {
-      getTopLogger().log(TreeLogger.ERROR, "Unable to create war directory "
-          + warDir);
+      getTopLogger().log(TreeLogger.ERROR,
+          "Unable to create war directory " + warDir);
       return -1;
     }
 
@@ -397,7 +399,7 @@
 
       ServletContainerLauncher scl = options.getServletContainerLauncher();
 
-      TreeLogger serverLogger = ui.getWebServerLogger(getWebServerName(), 
+      TreeLogger serverLogger = ui.getWebServerLogger(getWebServerName(),
           scl.getIconBytes());
 
       String sclArgs = options.getServletContainerLauncherArgs();
@@ -424,7 +426,10 @@
       clearCallback = false;
       return server.getPort();
     } catch (BindException e) {
-      System.err.println("Port " + bindAddress + ':' + getPort()
+      System.err.println("Port "
+          + bindAddress
+          + ':'
+          + getPort()
           + " is already is use; you probably still have another session active");
     } catch (Exception e) {
       System.err.println("Unable to start embedded HTTP server");
@@ -473,24 +478,35 @@
   protected synchronized void produceOutput(TreeLogger logger,
       StandardLinkerContext linkerStack, ArtifactSet artifacts,
       ModuleDef module, boolean isRelink) throws UnableToCompleteException {
+    TreeLogger linkLogger = logger.branch(TreeLogger.DEBUG, "Linking module '"
+        + module.getName() + "'");
+
     OutputFileSetOnDirectory outFileSet = new OutputFileSetOnDirectory(
         options.getWarDir(), module.getName() + "/");
-    linkerStack.produceOutput(logger, artifacts, false, outFileSet);
-    outFileSet.close();
-
+    OutputFileSet extraFileSet = new NullOutputFileSet();
     if (options.getExtraDir() != null) {
-      OutputFileSetOnDirectory extraFileSet = new OutputFileSetOnDirectory(
-          options.getExtraDir(), module.getName() + "/");
-      linkerStack.produceOutput(logger, artifacts, true, extraFileSet);
+      extraFileSet = new OutputFileSetOnDirectory(options.getExtraDir(),
+          module.getName() + "/");
+    }
+
+    linkerStack.produceOutput(linkLogger, artifacts, false, outFileSet);
+    linkerStack.produceOutput(linkLogger, artifacts, true, extraFileSet);
+
+    outFileSet.close();
+    try {
       extraFileSet.close();
+    } catch (IOException e) {
+      linkLogger.log(TreeLogger.ERROR, "Error emiting extra files", e);
+      throw new UnableToCompleteException();
     }
   }
 
   @Override
   protected void warnAboutNoStartupUrls() {
-    getTopLogger().log(TreeLogger.WARN,
+    getTopLogger().log(
+        TreeLogger.WARN,
         "No startup URLs supplied and no plausible ones found -- use "
-        + "-startupUrl");
+            + "-startupUrl");
   }
 
   private void validateServletTags(TreeLogger logger,
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index 9d02650..a70c7b0 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -151,8 +151,7 @@
           // replace a wildcard address with our machine's local address
           // this isn't fully accurate, as there is no guarantee we will get
           // the right one on a multihomed host
-          options.setConnectAddress(
-              InetAddress.getLocalHost().getHostAddress());
+          options.setConnectAddress(InetAddress.getLocalHost().getHostAddress());
         } else {
           options.setConnectAddress(value);
         }
@@ -565,7 +564,7 @@
 
   protected interface OptionBindAddress {
     String getBindAddress();
-    
+
     String getConnectAddress();
 
     void setBindAddress(String bindAddress);
@@ -744,8 +743,8 @@
    * Gets the base log level recommended by the UI for INFO-level messages. This
    * method can only be called once {@link #createUI()} has been called. Please
    * do not depend on this method, as it is subject to change.
-   *
-   * @return the log level to use for INFO-level messages 
+   * 
+   * @return the log level to use for INFO-level messages
    */
   public TreeLogger.Type getBaseLogLevelForUI() {
     if (baseLogLevelForUI == null) {
@@ -784,7 +783,7 @@
 
       // The web server is running now, so launch browsers for startup urls.
       ui.moduleLoadComplete(success);
-      
+
       blockUntilDone.acquire();
     } catch (Exception e) {
       e.printStackTrace();
@@ -837,9 +836,9 @@
   protected abstract void doShutDownServer();
 
   /**
-   * Perform any slower startup tasks, such as loading modules.  This is
-   * separate from {@link #doStartup()} so that the UI can be updated as
-   * soon as possible and the web server can be started earlier.
+   * Perform any slower startup tasks, such as loading modules. This is separate
+   * from {@link #doStartup()} so that the UI can be updated as soon as possible
+   * and the web server can be started earlier.
    * 
    * @return false if startup failed
    */
@@ -944,7 +943,10 @@
     // Create a new active linker stack for the fresh link.
     StandardLinkerContext linkerStack = new StandardLinkerContext(linkLogger,
         module, options);
-    ArtifactSet artifacts = linkerStack.invokeLink(linkLogger);
+    ArtifactSet artifacts = linkerStack.getArtifactsForPublicResources(logger,
+        module);
+    artifacts = linkerStack.invokeLegacyLinkers(linkLogger, artifacts);
+    artifacts = linkerStack.invokeFinalLink(linkLogger, artifacts);
     produceOutput(linkLogger, linkerStack, artifacts, module, false);
     return linkerStack;
   }
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index d5eea34..90c4330 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -69,8 +69,8 @@
   /**
    * Simple implementation of {@link LegacyCompilerOptions}.
    */
-  public static class GWTCompilerOptionsImpl extends PrecompileOptionsImpl implements
-      LegacyCompilerOptions {
+  public static class GWTCompilerOptionsImpl extends PrecompileOptionsImpl
+      implements LegacyCompilerOptions {
 
     private int localWorkers;
     private File outDir;
@@ -120,8 +120,8 @@
         public boolean run(TreeLogger logger) throws UnableToCompleteException {
           FutureTask<UpdateResult> updater = null;
           if (!options.isUpdateCheckDisabled()) {
-            updater = CheckForUpdates.checkForUpdatesInBackgroundThread(
-                logger, CheckForUpdates.ONE_DAY);
+            updater = CheckForUpdates.checkForUpdatesInBackgroundThread(logger,
+                CheckForUpdates.ONE_DAY);
           }
           boolean success = new GWTCompiler(options).run(logger);
           if (success) {
@@ -207,7 +207,7 @@
 
           Link.legacyLink(logger.branch(TreeLogger.TRACE, "Linking into "
               + options.getOutDir().getPath()), module, generatedArtifacts,
-              resultFiles, options.getOutDir(), precompileOptions);
+              allPerms, resultFiles, options.getOutDir(), precompileOptions);
 
           long compileDone = System.currentTimeMillis();
           long delta = compileDone - compileStart;
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 92a5ab7..8cf22f4 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -17,8 +17,12 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.Artifact;
 import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.core.ext.linker.impl.BinaryOnlyArtifactWrapper;
+import com.google.gwt.core.ext.linker.impl.JarEntryEmittedArtifact;
 import com.google.gwt.core.ext.linker.impl.StandardCompilationResult;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.dev.CompileTaskRunner.CompileTask;
@@ -42,13 +46,19 @@
 import com.google.gwt.dev.util.arg.OptionOutDir;
 import com.google.gwt.dev.util.arg.OptionWarDir;
 
+import java.io.BufferedInputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
 
 /**
  * Performs the last phase of compilation, merging the compilation outputs.
@@ -135,14 +145,14 @@
   }
 
   public static void legacyLink(TreeLogger logger, ModuleDef module,
-      ArtifactSet generatedArtifacts,
+      ArtifactSet generatedArtifacts, Permutation[] permutations,
       List<FileBackedObject<PermutationResult>> resultFiles, File outDir,
       JJSOptions precompileOptions) throws UnableToCompleteException,
       IOException {
     StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
         module, precompileOptions);
-    ArtifactSet artifacts = doLink(logger, linkerContext, generatedArtifacts,
-        resultFiles);
+    ArtifactSet artifacts = doSimulatedShardingLink(logger, module,
+        linkerContext, generatedArtifacts, permutations, resultFiles);
     OutputFileSet outFileSet = new OutputFileSetOnDirectory(outDir,
         module.getName() + "/");
     OutputFileSet extraFileSet = new OutputFileSetOnDirectory(outDir,
@@ -151,19 +161,85 @@
   }
 
   public static void link(TreeLogger logger, ModuleDef module,
-      ArtifactSet generatedArtifacts,
+      ArtifactSet generatedArtifacts, Permutation[] permutations,
       List<FileBackedObject<PermutationResult>> resultFiles, File outDir,
       File extrasDir, JJSOptions precompileOptions)
       throws UnableToCompleteException, IOException {
     StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
         module, precompileOptions);
-    ArtifactSet artifacts = doLink(logger, linkerContext, generatedArtifacts,
-        resultFiles);
+    ArtifactSet artifacts = doSimulatedShardingLink(logger, module,
+        linkerContext, generatedArtifacts, permutations, resultFiles);
     doProduceOutput(logger, artifacts, linkerContext, chooseOutputFileSet(
         outDir, module.getName() + "/"), chooseOutputFileSet(extrasDir,
         module.getName() + "/"));
   }
 
+  /**
+   * This link operation is performed on a CompilePerms shard for one
+   * permutation. It sees the generated artifacts for one permutation compile,
+   * and it runs the per-permutation part of each shardable linker.
+   */
+  @SuppressWarnings("unchecked")
+  public static void linkOnePermutationToJar(TreeLogger logger,
+      ModuleDef module, ArtifactSet generatedArtifacts,
+      PermutationResult permResult, File jarFile,
+      PrecompileOptions precompileOptions) throws UnableToCompleteException {
+    try {
+      JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile));
+
+      StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
+          module, precompileOptions);
+
+      StandardCompilationResult compilation = new StandardCompilationResult(
+          permResult);
+      addSelectionPermutations(compilation, permResult.getPermutation(),
+          linkerContext);
+      ArtifactSet permArtifacts = new ArtifactSet(generatedArtifacts);
+      permArtifacts.addAll(permResult.getArtifacts());
+      permArtifacts.add(compilation);
+
+      ArtifactSet linkedArtifacts = linkerContext.invokeLinkForOnePermutation(
+          logger, compilation, permArtifacts);
+
+      // Write the data of emitted artifacts
+      for (EmittedArtifact art : linkedArtifacts.find(EmittedArtifact.class)) {
+        String jarEntryPath;
+        if (art.isPrivate()) {
+          String pathWithLinkerName = linkerContext.getExtraPathForLinker(
+              art.getLinker(), art.getPartialPath());
+          if (pathWithLinkerName.startsWith("/")) {
+            // This happens if the linker has no extra path
+            pathWithLinkerName = pathWithLinkerName.substring(1);
+          }
+          jarEntryPath = "aux/" + pathWithLinkerName;
+        } else {
+          jarEntryPath = "target/" + art.getPartialPath();
+        }
+        jar.putNextEntry(new ZipEntry(jarEntryPath));
+        art.writeTo(logger, jar);
+        jar.closeEntry();
+      }
+
+      // Serialize artifacts marked as Transferable
+      int numSerializedArtifacts = 0;
+      // The raw type Artifact is to work around a Java compiler bug:
+      // http://bugs.sun.com/view_bug.do?bug_id=6548436
+      for (Artifact art : linkedArtifacts) {
+        if (art.isTransferableFromShards() && !(art instanceof EmittedArtifact)) {
+          String jarEntryPath = "arts/" + numSerializedArtifacts++;
+          jar.putNextEntry(new ZipEntry(jarEntryPath));
+          Util.writeObjectToStream(jar, art);
+          jar.closeEntry();
+        }
+      }
+
+      jar.close();
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Error linking", e);
+      throw new UnableToCompleteException();
+    }
+  }
+
   public static void main(String[] args) {
     /*
      * NOTE: main always exits with a call to System.exit to terminate any
@@ -189,6 +265,32 @@
   }
 
   /**
+   * In a parallel build, artifact sets are thinned down in transit between
+   * compilation and linking. All emitted artifacts are changed to binary
+   * emitted artifacts, and all other artifacts are dropped except @Transferable
+   * ones. This method simulates the thinning that happens in a parallel build.
+   */
+  @SuppressWarnings("unchecked")
+  public static ArtifactSet simulateTransferThinning(ArtifactSet artifacts,
+      StandardLinkerContext context) {
+    ArtifactSet thinnedArtifacts = new ArtifactSet();
+    // The raw type Artifact is to work around a compiler bug:
+    // http://bugs.sun.com/view_bug.do?bug_id=6548436
+    for (Artifact artifact : artifacts) {
+      if (artifact instanceof EmittedArtifact) {
+        EmittedArtifact emittedArtifact = (EmittedArtifact) artifact;
+        String path = getFullArtifactPath(emittedArtifact, context);
+        thinnedArtifacts.add(new BinaryOnlyArtifactWrapper(path,
+            emittedArtifact));
+      } else if (artifact.isTransferableFromShards()) {
+        thinnedArtifacts.add(artifact);
+      }
+    }
+
+    return thinnedArtifacts;
+  }
+
+  /**
    * Add to a compilation result all of the selection permutations from its
    * associated permutation.
    */
@@ -202,22 +304,11 @@
   }
 
   /**
-   * Choose an output file set for the given <code>dirOrJar</code> based on
-   * its name, whether it's null, and whether it already exists as a directory.
+   * Choose an output file set for the given <code>dirOrJar</code> based on its
+   * name, whether it's null, and whether it already exists as a directory.
    */
   private static OutputFileSet chooseOutputFileSet(File dirOrJar,
       String pathPrefix) throws IOException {
-    return chooseOutputFileSet(dirOrJar, pathPrefix, pathPrefix);
-  }
-
-  /**
-   * A version of {@link #chooseOutputFileSet(File, String)} that allows
-   * choosing a separate path prefix depending on whether the output is a
-   * directory or a jar file.
-   */
-  private static OutputFileSet chooseOutputFileSet(File dirOrJar,
-      String jarPathPrefix, String dirPathPrefix) throws IOException {
-
     if (dirOrJar == null) {
       return new NullOutputFileSet();
     }
@@ -225,10 +316,10 @@
     String name = dirOrJar.getName();
     if (!dirOrJar.isDirectory()
         && (name.endsWith(".war") || name.endsWith(".jar") || name.endsWith(".zip"))) {
-      return new OutputFileSetOnJar(dirOrJar, jarPathPrefix);
+      return new OutputFileSetOnJar(dirOrJar, pathPrefix);
     } else {
-      Util.recursiveDelete(new File(dirOrJar, dirPathPrefix), true);
-      return new OutputFileSetOnDirectory(dirOrJar, dirPathPrefix);
+      Util.recursiveDelete(new File(dirOrJar, pathPrefix), true);
+      return new OutputFileSetOnDirectory(dirOrJar, pathPrefix);
     }
   }
 
@@ -260,18 +351,6 @@
     return unboundProperties;
   }
 
-  private static ArtifactSet doLink(TreeLogger logger,
-      StandardLinkerContext linkerContext, ArtifactSet generatedArtifacts,
-      List<FileBackedObject<PermutationResult>> resultFiles)
-      throws UnableToCompleteException {
-    linkerContext.addOrReplaceArtifacts(generatedArtifacts);
-    for (FileBackedObject<PermutationResult> resultFile : resultFiles) {
-      PermutationResult result = resultFile.newInstance(logger);
-      finishPermutation(logger, result.getPermutation(), result, linkerContext);
-    }
-    return linkerContext.invokeLink(logger);
-  }
-
   /**
    * Emit final output.
    */
@@ -288,13 +367,70 @@
   }
 
   /**
-   * Add a compilation to a linker context.
+   * This link operation simulates sharded linking even though all generating
+   * and linking is happening on the same computer. It can tolerate
+   * non-shardable linkers.
    */
-  private static void finishPermutation(TreeLogger logger, Permutation perm,
-      PermutationResult permResult, StandardLinkerContext linkerContext) {
-    StandardCompilationResult compilation = linkerContext.getCompilation(permResult);
+  private static ArtifactSet doSimulatedShardingLink(TreeLogger logger,
+      ModuleDef module, StandardLinkerContext linkerContext,
+      ArtifactSet generatedArtifacts, Permutation[] perms,
+      List<FileBackedObject<PermutationResult>> resultFiles)
+      throws UnableToCompleteException {
+    ArtifactSet combinedArtifacts = new ArtifactSet();
+    for (int i = 0; i < perms.length; ++i) {
+      ArtifactSet newArtifacts = finishPermutation(logger, perms[i],
+          resultFiles.get(i), linkerContext, generatedArtifacts);
+      combinedArtifacts.addAll(newArtifacts);
+    }
+
+    combinedArtifacts.addAll(linkerContext.getArtifactsForPublicResources(
+        logger, module));
+
+    ArtifactSet legacyLinkedArtifacts = linkerContext.invokeLegacyLinkers(
+        logger, combinedArtifacts);
+
+    ArtifactSet thinnedArtifacts = simulateTransferThinning(
+        legacyLinkedArtifacts, linkerContext);
+
+    return linkerContext.invokeFinalLink(logger, thinnedArtifacts);
+  }
+
+  /**
+   * Add a compilation to a linker context. Also runs the shardable part of all
+   * linkers that support sharding.
+   * 
+   * @return the new artifacts generated by the shardable part of this link
+   *         operation
+   */
+  private static ArtifactSet finishPermutation(TreeLogger logger,
+      Permutation perm, FileBackedObject<PermutationResult> resultFile,
+      StandardLinkerContext linkerContext, ArtifactSet generatedArtifacts)
+      throws UnableToCompleteException {
+    PermutationResult permResult = resultFile.newInstance(logger);
+    StandardCompilationResult compilation = new StandardCompilationResult(
+        permResult);
     addSelectionPermutations(compilation, perm, linkerContext);
     logScriptSize(logger, perm.getId(), compilation);
+
+    ArtifactSet permArtifacts = new ArtifactSet(generatedArtifacts);
+    permArtifacts.addAll(permResult.getArtifacts());
+    permArtifacts.add(compilation);
+    permArtifacts.freeze();
+    return linkerContext.invokeLinkForOnePermutation(logger, compilation,
+        permArtifacts);
+  }
+
+  private static String getFullArtifactPath(EmittedArtifact emittedArtifact,
+      StandardLinkerContext context) {
+    String path = emittedArtifact.getPartialPath();
+    if (emittedArtifact.isPrivate()) {
+      path = context.getExtraPathForLinker(emittedArtifact.getLinker(), path);
+      if (path.startsWith("/")) {
+        // This happens if the linker has no extra path
+        path = path.substring(1);
+      }
+    }
+    return path;
   }
 
   /**
@@ -321,6 +457,54 @@
         + javaScript[0].length() + " and total script size of " + totalSize);
   }
 
+  private static ArtifactSet scanCompilePermResults(TreeLogger logger,
+      List<File> resultFiles) throws IOException, UnableToCompleteException {
+    final ArtifactSet artifacts = new ArtifactSet();
+
+    for (File resultFile : resultFiles) {
+      JarFile jarFile = new JarFile(resultFile);
+      Enumeration<JarEntry> entries = jarFile.entries();
+      while (entries.hasMoreElements()) {
+        JarEntry entry = entries.nextElement();
+        if (entry.isDirectory()) {
+          continue;
+        }
+
+        String path;
+        Artifact<?> artForEntry;
+
+        if (entry.getName().startsWith("target/")) {
+          path = entry.getName().substring("target/".length());
+          artForEntry = new JarEntryEmittedArtifact(path, resultFile, entry);
+        } else if (entry.getName().startsWith("aux/")) {
+          path = entry.getName().substring("aux/".length());
+          JarEntryEmittedArtifact jarArtifact = new JarEntryEmittedArtifact(
+              path, resultFile, entry);
+          jarArtifact.setPrivate(true);
+          artForEntry = jarArtifact;
+        } else if (entry.getName().startsWith("arts/")) {
+          try {
+            artForEntry = Util.readStreamAsObject(new BufferedInputStream(
+                jarFile.getInputStream(entry)), Artifact.class);
+            assert artForEntry.isTransferableFromShards();
+          } catch (ClassNotFoundException e) {
+            logger.log(TreeLogger.ERROR,
+                "Failed trying to deserialize an artifact", e);
+            throw new UnableToCompleteException();
+          }
+        } else {
+          continue;
+        }
+
+        artifacts.add(artForEntry);
+      }
+
+      jarFile.close();
+    }
+
+    return artifacts;
+  }
+
   private final LinkOptionsImpl options;
 
   public Link(LinkOptions options) {
@@ -328,44 +512,10 @@
   }
 
   public boolean run(TreeLogger logger) throws UnableToCompleteException {
-    for (String moduleName : options.getModuleNames()) {
+    loop_modules : for (String moduleName : options.getModuleNames()) {
       ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName);
 
-      OutputFileSet outFileSet;
-      OutputFileSet extraFileSet;
-      try {
-        if (options.getOutDir() == null) {
-          outFileSet = chooseOutputFileSet(options.getWarDir(),
-              module.getName() + "/");
-          extraFileSet = chooseOutputFileSet(options.getExtraDir(),
-              module.getName() + "/");
-        } else {
-          outFileSet = chooseOutputFileSet(options.getOutDir(),
-              module.getName() + "/");
-          if (options.getExtraDir() != null) {
-            extraFileSet = chooseOutputFileSet(options.getExtraDir(),
-                module.getName() + "-aux/", "");
-          } else if (outFileSet instanceof OutputFileSetOnDirectory) {
-            // Automatically emit extras into the output directory, if it's in
-            // fact a directory
-            extraFileSet = chooseOutputFileSet(options.getOutDir(),
-                module.getName() + "-aux/");
-          } else {
-            extraFileSet = new NullOutputFileSet();
-          }
-        }
-      } catch (IOException e) {
-        logger.log(TreeLogger.ERROR,
-            "Unexpected exception while producing output", e);
-        throw new UnableToCompleteException();
-      }
-
-      List<Permutation> permsList = new ArrayList<Permutation>();
-      ArtifactSet generatedArtifacts = new ArtifactSet();
-      JJSOptions precompileOptions = null;
-
       File compilerWorkDir = options.getCompilerWorkDir(moduleName);
-      List<Integer> permutationIds = new ArrayList<Integer>();
       PrecompilationResult precompileResults;
       try {
         precompileResults = Util.readFileAsObject(new File(compilerWorkDir,
@@ -384,53 +534,88 @@
         /**
          * Precompiling happened on the shards.
          */
-        precompileOptions = (JJSOptions) precompileResults;
-        int numPermutations = module.getProperties().numPermutations();
-        for (int i = 0; i < numPermutations; ++i) {
-          permutationIds.add(i);
+        if (!doLinkFinal(logger, compilerWorkDir, module,
+            (JJSOptions) precompileResults)) {
+          return false;
         }
+        continue loop_modules;
       } else {
         /**
          * Precompiling happened on the start node.
          */
-        Precompilation precompilation = (Precompilation) precompileResults;
-        permsList.addAll(Arrays.asList(precompilation.getPermutations()));
-        generatedArtifacts.addAll(precompilation.getGeneratedArtifacts());
-        precompileOptions = precompilation.getUnifiedAst().getOptions();
-
-        for (Permutation perm : precompilation.getPermutations()) {
-          permutationIds.add(perm.getId());
+        Precompilation precomp = (Precompilation) precompileResults;
+        Permutation[] perms = precomp.getPermutations();
+        List<FileBackedObject<PermutationResult>> resultFiles = CompilePerms.makeResultFiles(
+            compilerWorkDir, perms);
+        
+        // Check that all files are present
+        for (FileBackedObject<PermutationResult> file : resultFiles) {
+          if (!file.getFile().exists()) {
+            logger.log(TreeLogger.ERROR, "File not found '"
+                + file.getFile().getAbsolutePath()
+                + "'; please compile all permutations");
+            return false;
+          }
         }
-      }
 
-      List<FileBackedObject<PermutationResult>> resultFiles = new ArrayList<FileBackedObject<PermutationResult>>(
-          permutationIds.size());
-      for (int id : permutationIds) {
-        File f = CompilePerms.makePermFilename(compilerWorkDir, id);
-        if (!f.exists()) {
-          logger.log(TreeLogger.ERROR, "File not found '" + f.getAbsolutePath()
-              + "'; please compile all permutations");
-          return false;
+        TreeLogger branch = logger.branch(TreeLogger.INFO, "Linking module "
+            + module.getName());
+
+        try {
+          link(branch, module, precomp.getGeneratedArtifacts(), perms,
+              resultFiles, options.getWarDir(), options.getExtraDir(),
+              precomp.getUnifiedAst().getOptions());
+        } catch (IOException e) {
+          logger.log(TreeLogger.ERROR,
+              "Unexpected exception while producing output", e);
+          throw new UnableToCompleteException();
         }
-        resultFiles.add(new FileBackedObject<PermutationResult>(
-            PermutationResult.class, f));
-      }
-
-      TreeLogger branch = logger.branch(TreeLogger.INFO, "Linking module "
-          + module.getName());
-      StandardLinkerContext linkerContext = new StandardLinkerContext(branch,
-          module, precompileOptions);
-
-      ArtifactSet artifacts = doLink(branch, linkerContext, generatedArtifacts,
-          resultFiles);
-      try {
-        doProduceOutput(branch, artifacts, linkerContext, outFileSet,
-            extraFileSet);
-      } catch (IOException e) {
-        logger.log(TreeLogger.ERROR,
-            "Unexpected exception while producing output", e);
       }
     }
     return true;
   }
+
+  /**
+   * Do a final link, assuming the precompiles were done on the CompilePerms
+   * shards.
+   */
+  private boolean doLinkFinal(TreeLogger logger, File compilerWorkDir,
+      ModuleDef module, JJSOptions precompileOptions)
+      throws UnableToCompleteException {
+    int numPermutations = module.getProperties().numPermutations();
+    List<File> resultFiles = new ArrayList<File>(numPermutations);
+    for (int i = 0; i < numPermutations; ++i) {
+      File f = CompilePerms.makePermFilename(compilerWorkDir, i);
+      if (!f.exists()) {
+        logger.log(TreeLogger.ERROR, "File not found '" + f.getAbsolutePath()
+            + "'; please compile all permutations");
+        return false;
+      }
+      resultFiles.add(f);
+    }
+
+    TreeLogger branch = logger.branch(TreeLogger.INFO, "Linking module "
+        + module.getName());
+    StandardLinkerContext linkerContext = new StandardLinkerContext(branch,
+        module, precompileOptions);
+
+    try {
+      OutputFileSet outFileSet = chooseOutputFileSet(options.getWarDir(),
+          module.getName() + "/");
+      OutputFileSet extraFileSet = chooseOutputFileSet(options.getExtraDir(),
+          module.getName() + "/");
+
+      ArtifactSet artifacts = scanCompilePermResults(logger, resultFiles);
+      artifacts.addAll(linkerContext.getArtifactsForPublicResources(logger,
+          module));
+      artifacts = linkerContext.invokeFinalLink(logger, artifacts);
+      doProduceOutput(logger, artifacts, linkerContext, outFileSet,
+          extraFileSet);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Exception during final linking", e);
+      throw new UnableToCompleteException();
+    }
+
+    return true;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/PermutationWorker.java b/dev/core/src/com/google/gwt/dev/PermutationWorker.java
index 485c26c..9007d03 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationWorker.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationWorker.java
@@ -38,8 +38,8 @@
    * @throws UnableToCompleteException if the compile fails for any reason
    */
   void compile(TreeLogger logger, Permutation permutation,
-      FileBackedObject<PermutationResult> resultFile)
-      throws TransientWorkerException, UnableToCompleteException;
+      FileBackedObject<PermutationResult> resultFile) throws TransientWorkerException,
+      UnableToCompleteException;
 
   /**
    * Returns a human-readable description of the worker instance. This may be
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index 890038d..b2801f0 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -15,9 +15,11 @@
  */
 package com.google.gwt.dev;
 
+import com.google.gwt.core.ext.Linker;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.dev.CompileTaskRunner.CompileTask;
 import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.ConfigurationProperty;
@@ -47,6 +49,7 @@
 import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableCastChecking;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableClassMetadata;
+import com.google.gwt.dev.util.arg.ArgHandlerDisableGeneratingOnShards;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableRunAsync;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableUpdateCheck;
 import com.google.gwt.dev.util.arg.ArgHandlerDraftCompile;
@@ -54,8 +57,8 @@
 import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions;
 import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
 import com.google.gwt.dev.util.arg.ArgHandlerMaxPermsPerPrecompile;
-import com.google.gwt.dev.util.arg.ArgHandlerShardPrecompile;
 import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
+import com.google.gwt.dev.util.arg.ArgHandlerShardPrecompile;
 import com.google.gwt.dev.util.arg.ArgHandlerSoyc;
 import com.google.gwt.dev.util.arg.ArgHandlerSoycDetailed;
 import com.google.gwt.dev.util.arg.ArgHandlerValidateOnlyFlag;
@@ -97,7 +100,8 @@
       registerHandler(new ArgHandlerGenDir(options));
       registerHandler(new ArgHandlerScriptStyle(options));
       registerHandler(new ArgHandlerEnableAssertions(options));
-      registerHandler(new ArgHandlerShardPrecompile(options));
+      registerHandler(new ArgHandlerShardPrecompile());
+      registerHandler(new ArgHandlerDisableGeneratingOnShards(options));
       registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
       registerHandler(new ArgHandlerDisableClassMetadata(options));
       registerHandler(new ArgHandlerDisableCastChecking(options));
@@ -122,7 +126,7 @@
       PrecompileOptions, Serializable {
     private boolean disableUpdateCheck;
     private File dumpFile;
-    private boolean enableGeneratingOnShards;
+    private boolean enableGeneratingOnShards = true;
     private File genDir;
     private final JJSOptionsImpl jjsOptions = new JJSOptionsImpl();
     private int maxPermsPerPrecompile;
@@ -581,8 +585,23 @@
 
       ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName);
 
-      boolean generateOnShards = options.isEnabledGeneratingOnShards();
-      if (options.isValidateOnly()) {
+      StandardLinkerContext linkerContext = new StandardLinkerContext(
+          TreeLogger.NULL, module, options);
+
+      boolean generateOnShards = true;
+
+      if (!options.isEnabledGeneratingOnShards()) {
+        logger.log(TreeLogger.INFO, "Precompiling on the start node");
+        generateOnShards = false;
+      } else if (!linkerContext.allLinkersAreShardable()) {
+        TreeLogger legacyLinkersLogger = logger.branch(TreeLogger.INFO,
+            "Precompiling on the start node, because some linkers are not updated");
+        for (Linker linker : linkerContext.findUnshardableLinkers()) {
+          legacyLinkersLogger.log(TreeLogger.INFO, "Linker"
+              + linker.getClass().getCanonicalName() + " is not updated");
+        }
+        generateOnShards = false;
+      } else if (options.isValidateOnly()) {
         // Don't bother running on shards for just a validation run
         generateOnShards = false;
       } else if (options.getDumpSignatureFile() != null) {
diff --git a/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java b/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
index caacaec..65e8fe4 100644
--- a/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
+++ b/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
@@ -79,8 +79,7 @@
   @Override
   public Collection<PermutationWorker> getWorkers(TreeLogger logger,
       UnifiedAst unifiedAst, int numWorkers) throws UnableToCompleteException {
-    logger.log(TreeLogger.SPAM,
-        "Creating ThreadedPermutationWorkers");
+    logger.log(TreeLogger.SPAM, "Creating ThreadedPermutationWorkers");
 
     numWorkers = Math.min(numWorkers, Integer.getInteger(MAX_THREADS_PROPERTY,
         1));
@@ -100,8 +99,7 @@
 
   @Override
   public void init(TreeLogger logger) throws UnableToCompleteException {
-    logger.log(TreeLogger.SPAM,
-        "Initializing ThreadedPermutationWorkerFactory");
+    logger.log(TreeLogger.SPAM, "Initializing ThreadedPermutationWorkerFactory");
   }
 
   @Override
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 c7d07f5..c4bbe8a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -206,7 +206,7 @@
    * 
    * @param logger the logger to use
    * @param unifiedAst the result of a
-   *          {@link #precompile(TreeLogger, WebModeCompilerFrontEnd, String[], JJSOptions, boolean)}
+   *          {@link #precompile(TreeLogger, ModuleDef, RebindPermutationOracle, String[], String[], JJSOptions, boolean)}
    * @param permutation the permutation to compile
    * @return the output JavaScript
    * @throws UnableToCompleteException if an error other than
@@ -219,7 +219,8 @@
     PropertyOracle[] propertyOracles = permutation.getPropertyOracles();
     int permutationId = permutation.getId();
     Map<String, String> rebindAnswers = permutation.getRebindAnswers();
-    logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "...");
+    logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId
+        + "...");
     long permStart = System.currentTimeMillis();
     try {
       if (JProgram.isTracingEnabled()) {
@@ -660,7 +661,7 @@
       // remove same parameters value
       didChange = SameParameterValueOptimizer.exec(jprogram) || didChange;
     }
-    
+
     // prove that any types that have been culled from the main tree are
     // unreferenced due to type tightening?
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
index 269fdbd..b3543df 100644
--- a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.impl.HostedModeLinker;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.dev.cfg.ModuleDef;
@@ -552,12 +553,13 @@
       throws UnableToCompleteException {
     logger.log(TreeLogger.TRACE,
         "Generating a script selection script for module " + moduleName);
-
-    StandardLinkerContext context = new StandardLinkerContext(logger,
-        getModuleDef(logger, moduleName), new JJSOptionsImpl());
+    ModuleDef module = getModuleDef(logger, moduleName);
+    StandardLinkerContext context = new StandardLinkerContext(logger, module,
+        new JJSOptionsImpl());
+    ArtifactSet artifacts = context.getArtifactsForPublicResources(logger,
+        module);
     HostedModeLinker linker = new HostedModeLinker();
-    return linker.generateSelectionScript(logger, context,
-        context.getArtifacts());
+    return linker.generateSelectionScript(logger, context, artifacts);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/util/NullOutputFileSet.java b/dev/core/src/com/google/gwt/dev/util/NullOutputFileSet.java
index f7e7c4b..959465d 100644
--- a/dev/core/src/com/google/gwt/dev/util/NullOutputFileSet.java
+++ b/dev/core/src/com/google/gwt/dev/util/NullOutputFileSet.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.dev.util;
 
-import java.io.IOException;
 import java.io.OutputStream;
 
 /**
@@ -24,15 +23,15 @@
 public class NullOutputFileSet extends OutputFileSet {
   static class NullOutputStream extends OutputStream {
     @Override
-    public void write(byte[] b) throws IOException {
+    public void write(byte[] b) {
     }
 
     @Override
-    public void write(byte[] b, int i, int j) throws IOException {
+    public void write(byte[] b, int i, int j) {
     }
 
     @Override
-    public void write(int b) throws IOException {
+    public void write(int b) {
     }
   }
 
@@ -45,7 +44,8 @@
   }
 
   @Override
-  public OutputStream openForWrite(String path, long lastModifiedTime) {
+  protected OutputStream createNewOutputStream(String path,
+      long lastModifiedTime) {
     return new NullOutputStream();
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
index b29d355..04bff20 100644
--- a/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
@@ -17,17 +17,24 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * An abstract set of files that a linker links into.
  */
 public abstract class OutputFileSet {
   private final String pathDescription;
+  private final Set<String> pathsSeen = new HashSet<String>();
 
   protected OutputFileSet(String pathDescription) {
     this.pathDescription = pathDescription;
   }
 
+  public boolean alreadyContains(String path) {
+    return pathsSeen.contains(path);
+  }
+
   public abstract void close() throws IOException;
 
   /**
@@ -39,6 +46,17 @@
     return pathDescription;
   }
 
-  public abstract OutputStream openForWrite(String path, long lastModifiedTime)
-      throws IOException;
+  public final OutputStream openForWrite(String path) throws IOException {
+    int lastModifiedTime = -1;
+    return openForWrite(path, lastModifiedTime);
+  }
+
+  public OutputStream openForWrite(String path, long lastModifiedTime)
+      throws IOException {
+    pathsSeen.add(path);
+    return createNewOutputStream(path, lastModifiedTime);
+  }
+
+  protected abstract OutputStream createNewOutputStream(String path,
+      long lastModifiedTime) throws IOException;
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnDirectory.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnDirectory.java
index b4a1216..ade5ee3 100644
--- a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnDirectory.java
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnDirectory.java
@@ -43,12 +43,13 @@
   }
 
   @Override
-  public OutputStream openForWrite(String path, final long lastModifiedTime)
-      throws IOException {
-    final File file = makeFileForPath(path);
+  protected OutputStream createNewOutputStream(String path,
+      final long lastModifiedTime) throws IOException {
+    final File file = pathToFile(path);
     if (file.exists() && file.lastModified() >= lastModifiedTime) {
       return new NullOutputStream();
     }
+
     mkdirs(file.getParentFile());
     return new FileOutputStream(file) {
       @Override
@@ -59,14 +60,6 @@
     };
   }
 
-  private File makeFileForPath(String path) {
-    File file = dir;
-    for (String part : (prefix + path).split("/")) {
-      file = new File(file, part);
-    }
-    return file;
-  }
-
   /**
    * A faster bulk version of {@link File#mkdirs()} that avoids recreating the
    * same directory multiple times.
@@ -85,4 +78,12 @@
       dir.mkdir();
     }
   }
+
+  private File pathToFile(String path) {
+    File file = dir;
+    for (String part : (prefix + path).split("/")) {
+      file = new File(file, part);
+    }
+    return file;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
index 856533a..132c5bb 100644
--- a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
@@ -81,7 +81,7 @@
   }
 
   @Override
-  public OutputStream openForWrite(String path, long lastModifiedTime)
+  public OutputStream createNewOutputStream(String path, long lastModifiedTime)
       throws IOException {
     mkzipDirs(getParentPath(pathPrefix + path));
 
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 9713a20..2a9f5b5 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -49,6 +49,7 @@
 import java.io.Serializable;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
+import java.io.Writer;
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -1220,7 +1221,7 @@
     }
     objectStream.flush();
   }
-
+  
   public static boolean writeStringAsFile(File file, String string) {
     FileOutputStream stream = null;
     OutputStreamWriter writer = null;
@@ -1264,6 +1265,12 @@
       Utility.close(stream);
     }
   }
+
+  public static void writeStringToStream(OutputStream stream, String string) throws IOException {
+      Writer writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
+      writer.write(string);
+      writer.close();
+  }
   
   /**
    * Writes the contents of a StringBuilder to an OutputStream, encoding
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableGeneratingOnShards.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableGeneratingOnShards.java
new file mode 100644
index 0000000..47bd4f0
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableGeneratingOnShards.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2009 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.ArgHandlerFlag;
+
+/**
+ * An undocumented option to disable running generators on CompilePerms shards.
+ * This is present as a safety valve, in case something is new with the newer
+ * staging. Note that the old staging is used, regardless of this option's
+ * setting, if any linker is seen that has been updated. Thus, this option is
+ * useful only when all linkers have been updated but nonetheless there is a
+ * problem.
+ */
+public class ArgHandlerDisableGeneratingOnShards extends ArgHandlerFlag {
+  private OptionEnableGeneratingOnShards options;
+
+  public ArgHandlerDisableGeneratingOnShards(
+      OptionEnableGeneratingOnShards options) {
+    this.options = options;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "Disables running generators on CompilePerms shards, even when it would be a likely speedup";
+  }
+
+  @Override
+  public String getTag() {
+    return "-XdisableGeneratingOnShards";
+  }
+
+  @Override
+  public boolean isUndocumented() {
+    return true;
+  }
+
+  @Override
+  public boolean setFlag() {
+    options.setEnabledGeneratingOnShards(false);
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerShardPrecompile.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerShardPrecompile.java
index 706895d..98bc731 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerShardPrecompile.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerShardPrecompile.java
@@ -18,18 +18,12 @@
 import com.google.gwt.util.tools.ArgHandlerFlag;
 
 /**
- * An argument handler that enables running generators on shards.
+ * No effect. Only present for backwards compatibility.
  */
 public class ArgHandlerShardPrecompile extends ArgHandlerFlag {
-  private OptionEnableGeneratingOnShards options;
-
-  public ArgHandlerShardPrecompile(OptionEnableGeneratingOnShards options) {
-    this.options = options;
-  }
-
   @Override
   public String getPurpose() {
-    return "Enables running generators on CompilePerms shards";
+    return "No effect.  Only present for backwards compatibility.";
   }
 
   @Override
@@ -38,8 +32,12 @@
   }
 
   @Override
+  public boolean isUndocumented() {
+    return true;
+  }
+
+  @Override
   public boolean setFlag() {
-    options.setEnabledGeneratingOnShards(true);
     return true;
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java
index 2e8a84f..157703c 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 
 import junit.framework.TestCase;
@@ -33,6 +34,7 @@
  */
 public class ModuleDefTest extends TestCase {
 
+  @Shardable
   static class FakeLinker extends Linker {
     @Override
     public String getDescription() {
@@ -40,12 +42,6 @@
     }
 
     @Override
-    public ArtifactSet link(TreeLogger logger, LinkerContext context,
-        ArtifactSet artifacts) throws UnableToCompleteException {
-      return null;
-    }
-
-    @Override
     public ArtifactSet relink(TreeLogger logger, LinkerContext context,
         ArtifactSet newArtifacts) throws UnableToCompleteException {
       return null;
@@ -53,26 +49,32 @@
   }
 
   @LinkerOrder(Order.POST)
+  @Shardable
   static class FakeLinkerPost extends FakeLinker {
   }
 
   @LinkerOrder(Order.POST)
+  @Shardable
   static class FakeLinkerPost2 extends FakeLinker {
   }
 
   @LinkerOrder(Order.PRE)
+  @Shardable
   static class FakeLinkerPre extends FakeLinker {
   }
 
   @LinkerOrder(Order.PRE)
+  @Shardable
   static class FakeLinkerPre2 extends FakeLinker {
   }
 
   @LinkerOrder(Order.PRIMARY)
+  @Shardable
   static class FakeLinkerPrimary extends FakeLinker {
   }
 
   @LinkerOrder(Order.PRIMARY)
+  @Shardable
   static class FakeLinkerPrimary2 extends FakeLinker {
   }
 
diff --git a/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java b/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java
index 2a7fc93..723654c 100644
--- a/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java
+++ b/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java
@@ -20,6 +20,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.linker.SymbolMapsLinker;
 
 import java.io.ByteArrayOutputStream;
@@ -28,6 +29,7 @@
  * Emits the symbol maps into the application output directory so that the
  * JUnitHostImpl servlet can read them.
  */
+@Shardable
 public class JUnitSymbolMapsLinker extends SymbolMapsLinker {
   @Override
   protected void doEmitSymbolMap(TreeLogger logger, ArtifactSet artifacts,
diff --git a/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java b/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java
index 733c3f6..1727296 100644
--- a/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java
+++ b/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.core.ext.linker.SyntheticArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
@@ -37,6 +38,7 @@
  * Exports data required by server components for directly-evalable RPC.
  */
 @LinkerOrder(Order.PRE)
+@Shardable
 public class ClientOracleLinker extends AbstractLinker {
 
   private static final String SUFFIX = ".gwt.rpc";
@@ -48,42 +50,44 @@
 
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException {
-    artifacts = new ArtifactSet(artifacts);
+      ArtifactSet artifacts, boolean onePermutation)
+      throws UnableToCompleteException {
+    if (onePermutation) {
+      artifacts = new ArtifactSet(artifacts);
 
-    Map<String, List<String>> allSerializableFields = new HashMap<String, List<String>>();
+      Map<String, List<String>> allSerializableFields = new HashMap<String, List<String>>();
 
-    for (RpcDataArtifact data : artifacts.find(RpcDataArtifact.class)) {
-      allSerializableFields.putAll(data.getOperableFields());
+      for (RpcDataArtifact data : artifacts.find(RpcDataArtifact.class)) {
+        allSerializableFields.putAll(data.getOperableFields());
+      }
+
+      for (CompilationResult result : artifacts.find(CompilationResult.class)) {
+        Builder builder = new Builder();
+
+        for (Map.Entry<String, List<String>> entry : allSerializableFields.entrySet()) {
+          builder.setSerializableFields(entry.getKey(), entry.getValue());
+        }
+
+        for (SymbolData symbolData : result.getSymbolMap()) {
+          builder.add(symbolData.getSymbolName(), symbolData.getJsniIdent(),
+              symbolData.getClassName(), symbolData.getMemberName(),
+              symbolData.getTypeId());
+        }
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+          builder.getOracle().store(out);
+        } catch (IOException e) {
+          // Should generally not happen
+          logger.log(TreeLogger.ERROR, "Unable to store deRPC data", e);
+          throw new UnableToCompleteException();
+        }
+
+        SyntheticArtifact a = emitBytes(logger, out.toByteArray(),
+            result.getStrongName() + SUFFIX);
+        artifacts.add(a);
+      }
     }
-
-    for (CompilationResult result : artifacts.find(CompilationResult.class)) {
-      Builder builder = new Builder();
-
-      for (Map.Entry<String, List<String>> entry : allSerializableFields.entrySet()) {
-        builder.setSerializableFields(entry.getKey(), entry.getValue());
-      }
-
-      for (SymbolData symbolData : result.getSymbolMap()) {
-        builder.add(symbolData.getSymbolName(), symbolData.getJsniIdent(),
-            symbolData.getClassName(), symbolData.getMemberName(),
-            symbolData.getTypeId());
-      }
-
-      ByteArrayOutputStream out = new ByteArrayOutputStream();
-      try {
-        builder.getOracle().store(out);
-      } catch (IOException e) {
-        // Should generally not happen
-        logger.log(TreeLogger.ERROR, "Unable to store deRPC data", e);
-        throw new UnableToCompleteException();
-      }
-
-      SyntheticArtifact a = emitBytes(logger, out.toByteArray(),
-          result.getStrongName() + SUFFIX);
-      artifacts.add(a);
-    }
-
     return artifacts;
   }
 
diff --git a/user/src/com/google/gwt/rpc/rebind/RpcProxyCreator.java b/user/src/com/google/gwt/rpc/rebind/RpcProxyCreator.java
index 65dd36d..5203f94 100644
--- a/user/src/com/google/gwt/rpc/rebind/RpcProxyCreator.java
+++ b/user/src/com/google/gwt/rpc/rebind/RpcProxyCreator.java
@@ -38,6 +38,7 @@
 import com.google.gwt.rpc.linker.RpcDataArtifact;
 import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
+import com.google.gwt.user.linker.rpc.RpcLogArtifact;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.rpc.CustomFieldSerializerValidator;
@@ -296,7 +297,7 @@
 
     ctx.commitArtifact(logger, data);
 
-    return "unused";
+    return RpcLogArtifact.UNSPECIFIED_STRONGNAME;
   }
 
   private StringBuilder writeArtificialRescues(TypeOracle typeOracle,
diff --git a/user/src/com/google/gwt/user/RemoteService.gwt.xml b/user/src/com/google/gwt/user/RemoteService.gwt.xml
index 8899cb5..af6d99b 100644
--- a/user/src/com/google/gwt/user/RemoteService.gwt.xml
+++ b/user/src/com/google/gwt/user/RemoteService.gwt.xml
@@ -58,6 +58,9 @@
 		<when-type-assignable class="com.google.gwt.user.client.rpc.RemoteService"/>
 	</generate-with>
   
+    <define-linker name="rpcLog" class="com.google.gwt.user.linker.rpc.RpcLogLinker" />
+    <add-linker name="rpcLog" />
+
     <define-linker name="rpcPolicyManifest" class="com.google.gwt.user.linker.rpc.RpcPolicyManifestLinker" />
     <add-linker name="rpcPolicyManifest" /> 
 </module>
diff --git a/user/src/com/google/gwt/user/linker/rpc/RpcLogArtifact.java b/user/src/com/google/gwt/user/linker/rpc/RpcLogArtifact.java
new file mode 100644
index 0000000..052a6f53
--- /dev/null
+++ b/user/src/com/google/gwt/user/linker/rpc/RpcLogArtifact.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2009 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.user.linker.rpc;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.linker.Artifact;
+import com.google.gwt.dev.util.DiskCache;
+import com.google.gwt.dev.util.Util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * This artifact holds a log of the reasoning for which types are considered
+ * serializable for a particular RPC interface.
+ */
+public class RpcLogArtifact extends Artifact<RpcLogArtifact> {
+  /**
+   * This strong name indicates that the artifact doesn't really have its own
+   * strong name.
+   */
+  public static final String UNSPECIFIED_STRONGNAME = "UNSPECIFIED";
+  private static DiskCache diskCache = new DiskCache();
+
+  private long diskCacheToken;
+  private final String qualifiedSourceName;
+  private final String serializationPolicyStrongName;
+
+  public RpcLogArtifact(String qualifiedSourceName,
+      String serializationPolicyStrongName, String rpcLog) {
+    super(RpcLogLinker.class);
+    this.qualifiedSourceName = qualifiedSourceName;
+    this.serializationPolicyStrongName = serializationPolicyStrongName;
+    diskCacheToken = diskCache.writeString(rpcLog);
+  }
+
+  public InputStream getContents(TreeLogger logger) {
+    return new ByteArrayInputStream(diskCache.readByteArray(diskCacheToken));
+  }
+
+  public String getQualifiedSourceName() {
+    return qualifiedSourceName;
+  }
+
+  public String getSerializationPolicyStrongName() {
+    return serializationPolicyStrongName;
+  }
+
+  @Override
+  public int hashCode() {
+    return serializationPolicyStrongName.hashCode();
+  }
+
+  @Override
+  protected int compareToComparableArtifact(RpcLogArtifact o) {
+    int comp;
+    comp = qualifiedSourceName.compareTo(o.getQualifiedSourceName());
+    if (comp != 0) {
+      return comp;
+    }
+    return serializationPolicyStrongName.compareTo(o.getSerializationPolicyStrongName());
+  }
+
+  @Override
+  protected Class<RpcLogArtifact> getComparableArtifactType() {
+    return RpcLogArtifact.class;
+  }
+
+  private void readObject(ObjectInputStream stream) throws IOException,
+      ClassNotFoundException {
+    stream.defaultReadObject();
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    Util.copyNoClose(stream, baos);
+    diskCacheToken = diskCache.writeByteArray(baos.toByteArray());
+  }
+
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    stream.defaultWriteObject();
+    diskCache.transferToStream(diskCacheToken, stream);
+  }
+}
diff --git a/user/src/com/google/gwt/user/linker/rpc/RpcLogLinker.java b/user/src/com/google/gwt/user/linker/rpc/RpcLogLinker.java
new file mode 100644
index 0000000..58c70e7
--- /dev/null
+++ b/user/src/com/google/gwt/user/linker/rpc/RpcLogLinker.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2009 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.user.linker.rpc;
+
+import com.google.gwt.core.ext.LinkerContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.AbstractLinker;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.CompilationResult;
+import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.Shardable;
+import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+
+/**
+ * This linker emits {@link RpcLogArtifact}s as output files.
+ */
+@LinkerOrder(Order.POST)
+@Shardable
+public class RpcLogLinker extends AbstractLinker {
+
+  @Override
+  public String getDescription() {
+    return "RPC log linker";
+  }
+
+  @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts, boolean onePermutation)
+      throws UnableToCompleteException {
+    if (onePermutation) {
+      ArtifactSet toReturn = new ArtifactSet(artifacts);
+      logger = logger.branch(TreeLogger.TRACE, "Emitting RPC log files");
+
+      for (CompilationResult result : artifacts.find(CompilationResult.class)) {
+        for (RpcLogArtifact logArt : artifacts.find(RpcLogArtifact.class)) {
+          String policyStrongName = logArt.getSerializationPolicyStrongName();
+          if (policyStrongName.equals(RpcLogArtifact.UNSPECIFIED_STRONGNAME)) {
+            /*
+             * If the artifact has no strong name of its own, use the
+             * compilation strong name.
+             */
+            policyStrongName = result.getStrongName();
+          }
+          EmittedArtifact art = emitInputStream(logger,
+              logArt.getContents(logger), logArt.getQualifiedSourceName() + "-"
+                  + policyStrongName + ".rpc.log");
+          art.setPrivate(true);
+          toReturn.add(art);
+        }
+      }
+
+      return toReturn;
+    } else {
+      return artifacts;
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java b/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java
index 9a841b2..8adb7f4 100644
--- a/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java
+++ b/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java
@@ -22,14 +22,34 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.Shardable;
+import com.google.gwt.core.ext.linker.SyntheticArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.user.rebind.rpc.ProxyCreator;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
- * Emit a file contating a map of RPC proxy classes to the partial path of the
+ * Emit a file containing a map of RPC proxy classes to the partial path of the
  * RPC policy file.
  */
 @LinkerOrder(Order.PRE)
+@Shardable
 public class RpcPolicyManifestLinker extends AbstractLinker {
+  private static final String MANIFEST_TXT = "manifest.txt";
+
+  /**
+   * The main body of the manifest. It is built up as per-permutation manifests
+   * are looped over.
+   */
+  private StringBuilder manifestBody = new StringBuilder();
 
   @Override
   public String getDescription() {
@@ -38,26 +58,79 @@
 
   @Override
   public ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException {
-    artifacts = new ArtifactSet(artifacts);
+      ArtifactSet artifacts, boolean onePermutation)
+      throws UnableToCompleteException {
+    if (onePermutation) {
+      return artifacts;
+    } else {
+      for (EmittedArtifact art : artifacts.find(EmittedArtifact.class)) {
+        if (art.getPartialPath().startsWith(ProxyCreator.MANIFEST_ARTIFACT_DIR)) {
+          readOneManifest(logger, art.getContents(logger));
+        }
+      }
 
-    StringBuilder contents = new StringBuilder();
-    contents.append("# Module ").append(context.getModuleName()).append("\n");
-    contents.append("# RPC service class, partial path of RPC policy file\n");
+      ArtifactSet toReturn = new ArtifactSet(artifacts);
+      SyntheticArtifact manifestArt = emitString(logger,
+          generateManifest(context), MANIFEST_TXT);
+      manifestArt.setPrivate(true);
+      toReturn.add(manifestArt);
+      return toReturn;
+    }
+  }
 
-    // Loop over all policy file artifacts
-    for (RpcPolicyFileArtifact artifact : artifacts.find(RpcPolicyFileArtifact.class)) {
-      // com.foo.Service, 12345.rpc.txt
-      contents.append(artifact.getProxyClass()).append(", ").append(
-          artifact.getEmittedArtifact().getPartialPath()).append("\n");
+  /**
+   * Compute a manifest for all RPC policy files seen so far.
+   */
+  private String generateManifest(LinkerContext context) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("# Module " + context.getModuleName() + "\n");
+    sb.append("# RPC service class, partial path of RPC policy file\n");
+    sb.append(manifestBody.toString());
+    return sb.toString();
+  }
+
+  /**
+   * Read one manifest and close the input stream.
+   */
+  private void readOneManifest(TreeLogger logger, InputStream manifestStream)
+      throws UnableToCompleteException {
+    Map<String, String> entries = new HashMap<String, String>();
+    try {
+      BufferedReader reader = new BufferedReader(new InputStreamReader(
+          manifestStream, Util.DEFAULT_ENCODING));
+      String line;
+      while ((line = reader.readLine()) != null) {
+        int idx = line.indexOf(':');
+        if (idx < 0) {
+          throw new InternalCompilerException(
+              "invalid selection information line: " + line);
+        }
+        String propName = line.substring(0, idx).trim();
+        String propValue = line.substring(idx + 1).trim();
+        entries.put(propName, propValue);
+      }
+      reader.close();
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unexpected IOException", e);
+      throw new UnableToCompleteException();
     }
 
-    // The name of the linker is prepended as a directory prefix
-    EmittedArtifact manifest = emitString(logger, contents.toString(),
-        "manifest.txt");
-    manifest.setPrivate(true);
-    artifacts.add(manifest);
+    String serviceClass = entries.get("serviceClass");
+    if (serviceClass == null) {
+      logger.log(TreeLogger.ERROR,
+          "Internal error: manifest file does not include a serviceClass");
+      throw new UnableToCompleteException();
+    }
+    String path = entries.get("path");
+    if (path == null) {
+      logger.log(TreeLogger.ERROR,
+          "Internal error: manifest file does not include a path");
+      throw new UnableToCompleteException();
+    }
 
-    return artifacts;
+    manifestBody.append(serviceClass);
+    manifestBody.append(", ");
+    manifestBody.append(path);
+    manifestBody.append("\n");
   }
 }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
index d59e6f7..6a67d73 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -48,7 +48,7 @@
 import com.google.gwt.user.client.rpc.impl.FailingRequestBuilder;
 import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
 import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
-import com.google.gwt.user.linker.rpc.RpcPolicyFileArtifact;
+import com.google.gwt.user.linker.rpc.RpcLogArtifact;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
@@ -59,7 +59,9 @@
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
+import java.io.Writer;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -72,6 +74,11 @@
  * as well as the necessary type and field serializers.
  */
 public class ProxyCreator {
+  /**
+   * The directory within which RPC manifests are placed for individual
+   * permutations.
+   */
+  public static final String MANIFEST_ARTIFACT_DIR = "rpcPolicyManifest/manifests";
 
   private static final Map<JPrimitiveType, ResponseReader> JPRIMITIVETYPE_TO_RESPONSEREADER = new HashMap<JPrimitiveType, ResponseReader>();
 
@@ -263,19 +270,17 @@
       throw new UnableToCompleteException();
     }
 
-    // Create a resource file to receive all of the serialization information
-    // computed by STOB and mark it as private so it does not end up in the
-    // output.
-    OutputStream pathInfo = context.tryCreateResource(logger,
-        serviceIntf.getQualifiedSourceName() + ".rpc.log");
-    PrintWriter writer = null;
+    // Decide what types to send in each direction.
+    // Log the decisions to a string that will be written later in this method
     SerializableTypeOracle typesSentFromBrowser;
     SerializableTypeOracle typesSentToBrowser;
-    try {
-      writer = new PrintWriter(pathInfo);
+    String rpcLog;
+    {
+      StringWriter stringWriter = new StringWriter();
+      PrintWriter writer = new PrintWriter(stringWriter);
 
-      typesSentFromBrowserBuilder.setLogOutputStream(pathInfo);
-      typesSentToBrowserBuilder.setLogOutputStream(pathInfo);
+      typesSentFromBrowserBuilder.setLogOutputWriter(writer);
+      typesSentToBrowserBuilder.setLogOutputWriter(writer);
 
       writer.write("====================================\n");
       writer.write("Types potentially sent from browser:\n");
@@ -289,13 +294,8 @@
       writer.flush();
       typesSentToBrowser = typesSentToBrowserBuilder.build(logger);
 
-      if (pathInfo != null) {
-        context.commitResource(logger, pathInfo).setPrivate(true);
-      }
-    } finally {
-      if (writer != null) {
-        writer.close();
-      }
+      writer.close();
+      rpcLog = stringWriter.toString();
     }
 
     generateTypeHandlers(logger, context, typesSentFromBrowser,
@@ -321,6 +321,12 @@
 
     srcWriter.commit(logger);
 
+    // Create an artifact explaining STOB's decisions. It will be emitted by
+    // RpcLogLinker
+    context.commitArtifact(logger, new RpcLogArtifact(
+        serviceIntf.getQualifiedSourceName(), serializationPolicyStrongName,
+        rpcLog));
+
     return getProxyQualifiedName();
   }
 
@@ -620,7 +626,7 @@
   protected Class<? extends SerializationStreamWriter> getStreamWriterClass() {
     return ClientSerializationStreamWriter.class;
   }
-  
+
   protected String writeSerializationPolicyFile(TreeLogger logger,
       GeneratorContext ctx, SerializableTypeOracle serializationSto,
       SerializableTypeOracle deserializationSto)
@@ -663,8 +669,7 @@
          * containing the keyword '@ClientFields', the class name, and a list of
          * all potentially serializable client-visible fields.
          */
-        if ((type instanceof JClassType)
-            && ((JClassType) type).isEnhanced()) {
+        if ((type instanceof JClassType) && ((JClassType) type).isEnhanced()) {
           JField[] fields = ((JClassType) type).getFields();
           JField[] rpcFields = new JField[fields.length];
           int numRpcFields = 0;
@@ -703,8 +708,7 @@
          * Record which proxy class created the resource. A manifest will be
          * emitted by the RpcPolicyManifestLinker.
          */
-        ctx.commitArtifact(logger, new RpcPolicyFileArtifact(
-            serviceIntf.getQualifiedSourceName(), resource));
+        emitPolicyFileArtifact(logger, ctx, resource.getPartialPath());
       } else {
         logger.log(TreeLogger.TRACE,
             "SerializationPolicy file for RemoteService '"
@@ -724,6 +728,38 @@
     }
   }
 
+  private void emitPolicyFileArtifact(TreeLogger logger,
+      GeneratorContext context, String partialPath)
+      throws UnableToCompleteException {
+    try {
+      String qualifiedSourceName = serviceIntf.getQualifiedSourceName();
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      Writer writer;
+      writer = new OutputStreamWriter(baos,
+          SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
+      writer.write("serviceClass: " + qualifiedSourceName + "\n");
+      writer.write("path: " + partialPath + "\n");
+      writer.close();
+
+      byte[] manifestBytes = baos.toByteArray();
+      String md5 = Util.computeStrongName(manifestBytes);
+      OutputStream os = context.tryCreateResource(logger, MANIFEST_ARTIFACT_DIR
+          + "/" + md5 + ".txt");
+      os.write(manifestBytes);
+
+      GeneratedResource resource = context.commitResource(logger, os);
+      resource.setPrivate(true);
+    } catch (UnsupportedEncodingException e) {
+      logger.log(TreeLogger.ERROR,
+          SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
+              + " is not supported", e);
+      throw new UnableToCompleteException();
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, null, e);
+      throw new UnableToCompleteException();
+    }
+  }
+
   private String getProxyQualifiedName() {
     String[] name = Shared.synthesizeTopLevelClassName(serviceIntf,
         PROXY_SUFFIX);
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index f952c9d..e97a099 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -37,7 +37,6 @@
 import com.google.gwt.user.rebind.rpc.TypeParameterExposureComputer.TypeParameterFlowInfo;
 import com.google.gwt.user.rebind.rpc.TypePaths.TypePath;
 
-import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
@@ -60,13 +59,13 @@
 
 /**
  * Builds a {@link SerializableTypeOracle} for a given set of root types.
- *
+ * 
  * <p>
  * There are two goals for this builder. First, discover the set of serializable
  * types that can be serialized if you serialize one of the root types. Second,
  * to make sure that all root types can actually be serialized by GWT.
  * </p>
- *
+ * 
  * <p>
  * To find the serializable types, it includes the root types, and then it
  * iteratively traverses the type hierarchy and the fields of any type already
@@ -75,7 +74,7 @@
  * parameterized type, these exposure values are used to determine how to treat
  * the arguments.
  * </p>
- *
+ * 
  * <p>
  * A type qualifies for serialization if it or one of its subtypes is
  * automatically or manually serializable. Automatic serialization is selected
@@ -86,19 +85,19 @@
  * qualifies for both manual and automatic serialization, manual serialization
  * is preferred.
  * </p>
- *
+ * 
  * <p>
  * Some types may be marked as "enhanced," either automatically by the presence
  * of a JDO <code>@PersistenceCapable</code> or JPA <code>@Entity</code> tag on
  * the class definition, or manually by extending the 'rpc.enhancedClasses'
  * configuration property in the GWT module XML file. For example, to manually
  * mark the class com.google.myapp.MyPersistentClass as enhanced, use:
- *
+ * 
  * <pre>
  * <extend-configuration-property name='rpc.enhancedClasses'
  *     value='com.google.myapp.MyPersistentClass'/>
  * </pre>
- *
+ * 
  * <p>
  * Enhanced classes are checked for the presence of additional serializable
  * fields on the server that were not defined in client code as seen by the GWT
@@ -117,12 +116,6 @@
 
   class TypeInfoComputed {
     /**
-     * All instantiable types found when this type was quaried, including the
-     * type itself.
-     */
-    private Set<JClassType> instantiableTypes = new HashSet<JClassType>();
-
-    /**
      * <code>true</code> if the type is assignable to {@link IsSerializable} or
      * {@link java.io.Serializable Serializable}.
      */
@@ -152,6 +145,12 @@
     private boolean instantiableSubtypes;
 
     /**
+     * All instantiable types found when this type was quaried, including the
+     * type itself.
+     */
+    private Set<JClassType> instantiableTypes = new HashSet<JClassType>();
+
+    /**
      * Custom field serializer or <code>null</code> if there isn't one.
      */
     private final JClassType manualSerializer;
@@ -198,9 +197,9 @@
     /**
      * Returns the internal set of instantiable types for this TIC.
      * Modifications to this set are immediately recorded into the TIC as well.
-     * TODO(spoon) maybe pass the TIC around instead of the set?
-     *  then there could be addInstantiableType(JClassType) instead of
-     *  speccing this to be mutable.
+     * TODO(spoon) maybe pass the TIC around instead of the set? then there
+     * could be addInstantiableType(JClassType) instead of speccing this to be
+     * mutable.
      */
     public Set<JClassType> getInstantiableTypes() {
       return instantiableTypes;
@@ -352,8 +351,8 @@
       JDO_PERSISTENCE_CAPABLE_ANNOTATION = Class.forName(
           "javax.jdo.annotations.PersistenceCapable").asSubclass(
           Annotation.class);
-      JDO_PERSISTENCE_CAPABLE_DETACHABLE_METHOD =
-          JDO_PERSISTENCE_CAPABLE_ANNOTATION.getDeclaredMethod("detachable", (Class[]) null);
+      JDO_PERSISTENCE_CAPABLE_DETACHABLE_METHOD = JDO_PERSISTENCE_CAPABLE_ANNOTATION.getDeclaredMethod(
+          "detachable", (Class[]) null);
     } catch (ClassNotFoundException e) {
       // Ignore, JDO_PERSISTENCE_CAPABLE_ANNOTATION will be null
     } catch (NoSuchMethodException e) {
@@ -378,8 +377,8 @@
 
       if (!type.isDefaultInstantiable() && !isManuallySerializable(type)) {
         // Warn and return false.
-        problems.add(type, type.getParameterizedQualifiedSourceName() +
-            " is not default instantiable (it must have a zero-argument "
+        problems.add(type, type.getParameterizedQualifiedSourceName()
+            + " is not default instantiable (it must have a zero-argument "
             + "constructor or no constructors at all) and has no custom "
             + "serializer.", Priority.DEFAULT);
         return false;
@@ -396,13 +395,14 @@
 
   /**
    * Finds the custom field serializer for a given type.
-   *
+   * 
    * @param typeOracle
    * @param type
    * @return the custom field serializer for a type or <code>null</code> if
    *         there is not one
    */
-  public static JClassType findCustomFieldSerializer(TypeOracle typeOracle, JType type) {
+  public static JClassType findCustomFieldSerializer(TypeOracle typeOracle,
+      JType type) {
     JClassType classOrInterface = type.isClassOrInterface();
     if (classOrInterface == null) {
       return null;
@@ -532,7 +532,7 @@
    * this class should be serialized.
    */
   static boolean shouldConsiderFieldsForSerialization(JClassType type,
-       TypeFilter filter, ProblemReport problems) {
+      TypeFilter filter, ProblemReport problems) {
     if (!isAllowedByFilter(filter, type, problems)) {
       return false;
     }
@@ -564,18 +564,18 @@
       if (!isAccessibleToSerializer(type)) {
         // Class is not visible to a serializer class in the same package
         problems.add(type, type.getParameterizedQualifiedSourceName()
-                + " is not accessible from a class in its same package; it "
-                + "will be excluded from the set of serializable types",
+            + " is not accessible from a class in its same package; it "
+            + "will be excluded from the set of serializable types",
             Priority.DEFAULT);
         return false;
       }
 
       if (type.isMemberType() && !type.isStatic()) {
         // Non-static member types cannot be serialized
-        problems.add(type,
-            type.getParameterizedQualifiedSourceName() + " is nested but " +
-            "not static; it will be excluded from the set of serializable " +
-            "types", Priority.DEFAULT);
+        problems.add(type, type.getParameterizedQualifiedSourceName()
+            + " is nested but "
+            + "not static; it will be excluded from the set of serializable "
+            + "types", Priority.DEFAULT);
         return false;
       }
     }
@@ -674,11 +674,10 @@
   }
 
   private static boolean isAllowedByFilter(TypeFilter filter,
-        JClassType classType, ProblemReport problems) {
+      JClassType classType, ProblemReport problems) {
     if (!filter.isAllowed(classType)) {
-      problems.add(classType,
-          classType.getParameterizedQualifiedSourceName()
-             + " is excluded by type filter ", Priority.AUXILIARY);
+      problems.add(classType, classType.getParameterizedQualifiedSourceName()
+          + " is excluded by type filter ", Priority.AUXILIARY);
       return false;
     }
 
@@ -716,10 +715,10 @@
    * Cache of the {@link JClassType} for {@link Collection}.
    */
   private final JGenericType collectionClass;
-  
+
   private Set<String> enhancedClasses = null;
 
-  private OutputStream logOutputStream;
+  private PrintWriter logOutputWriter;
 
   /**
    * Cache of the {@link JClassType} for {@link Map}.
@@ -757,11 +756,11 @@
 
   /**
    * Constructs a builder.
-   *
+   * 
    * @param logger
    * @param propertyOracle
    * @param typeOracle
-   *
+   * 
    * @throws UnableToCompleteException if we fail to find one of our special
    *           types
    */
@@ -802,12 +801,12 @@
 
   /**
    * Builds a {@link SerializableTypeOracle} for a given set of root types.
-   *
+   * 
    * @param logger
-   *
+   * 
    * @return a {@link SerializableTypeOracle} for the specified set of root
    *         types
-   *
+   * 
    * @throws UnableToCompleteException if there was not at least one
    *           instantiable type assignable to each of the specified root types
    */
@@ -892,11 +891,11 @@
   }
 
   /**
-   * Set the {@link OutputStream} which will receive a detailed log of the types
+   * Set the {@link PrintWriter} which will receive a detailed log of the types
    * which were examined in order to determine serializability.
    */
-  public void setLogOutputStream(OutputStream logOutputStream) {
-    this.logOutputStream = logOutputStream;
+  public void setLogOutputWriter(PrintWriter logOutputWriter) {
+    this.logOutputWriter = logOutputWriter;
   }
 
   public void setTypeFilter(TypeFilter typeFilter) {
@@ -965,7 +964,8 @@
     if (isWildcard != null) {
       boolean success = true;
       for (JClassType bound : isWildcard.getUpperBounds()) {
-        success &= computeTypeInstantiability(localLogger, bound, path, problems).hasInstantiableSubtypes();
+        success &= computeTypeInstantiability(localLogger, bound, path,
+            problems).hasInstantiableSubtypes();
       }
       tic = getTypeInfoComputed(classType, path, true);
       tic.setInstantiableSubtypes(success);
@@ -975,8 +975,8 @@
 
     JArrayType isArray = classType.isArray();
     if (isArray != null) {
-      TypeInfoComputed arrayTic = checkArrayInstantiable(localLogger, isArray, path,
-          problems);
+      TypeInfoComputed arrayTic = checkArrayInstantiable(localLogger, isArray,
+          path, problems);
       assert getTypeInfoComputed(classType, path, false) != null;
       return arrayTic;
     }
@@ -984,12 +984,12 @@
     if (classType == typeOracle.getJavaLangObject()) {
       /*
        * Report an error if the type is or erases to Object since this violates
-       * our restrictions on RPC.  Should be fatal, but I worry users may have
+       * our restrictions on RPC. Should be fatal, but I worry users may have
        * Object-using code they can't readily get out of the class hierarchy.
        */
       problems.add(classType,
-          "In order to produce smaller client-side code, 'Object' is not " +
-          "allowed; please use a more specific type", Priority.DEFAULT);
+          "In order to produce smaller client-side code, 'Object' is not "
+              + "allowed; please use a more specific type", Priority.DEFAULT);
       tic = getTypeInfoComputed(classType, path, true);
       tic.setInstantiable(false);
       return tic;
@@ -1025,18 +1025,17 @@
   /**
    * Returns <code>true</code> if the fields of the type should be considered
    * for serialization.
-   *
+   * 
    * Default access to allow for testing.
    */
   boolean shouldConsiderFieldsForSerialization(JClassType type,
       ProblemReport problems) {
-    return shouldConsiderFieldsForSerialization(type, typeFilter,
-        problems);
+    return shouldConsiderFieldsForSerialization(type, typeFilter, problems);
   }
 
   /**
    * Consider any subtype of java.lang.Object which qualifies for serialization.
-   *
+   * 
    * @param logger
    */
   private void checkAllSubtypesOfObject(TreeLogger logger, TypePath parent,
@@ -1064,8 +1063,8 @@
     }
   }
 
-  private TypeInfoComputed checkArrayInstantiable(TreeLogger logger, JArrayType array,
-      TypePath path, ProblemReport problems) {
+  private TypeInfoComputed checkArrayInstantiable(TreeLogger logger,
+      JArrayType array, TypePath path, ProblemReport problems) {
 
     JType leafType = array.getLeafType();
     JWildcardType leafWild = leafType.isWildcard();
@@ -1080,7 +1079,7 @@
     if (isLeafTypeParameter != null
         && !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
       // Don't deal with non root type parameters, but make a TIC entry to
-      // save time if it recurs.  We assume they're indirectly instantiable.
+      // save time if it recurs. We assume they're indirectly instantiable.
       TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
       tic.setInstantiableSubtypes(true);
       tic.setInstantiable(false);
@@ -1089,7 +1088,7 @@
 
     if (!isAllowedByFilter(array, problems)) {
       // Don't deal with filtered out types either, but make a TIC entry to
-      // save time if it recurs.  We assume they're not instantiable.
+      // save time if it recurs. We assume they're not instantiable.
       TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
       tic.setInstantiable(false);
       return tic;
@@ -1183,8 +1182,8 @@
               "Object was reached from a manually serializable type", null),
               path, problems);
         } else {
-          allSucceeded &= computeTypeInstantiability(fieldLogger, fieldType, path,
-              problems).hasInstantiableSubtypes();
+          allSucceeded &= computeTypeInstantiability(fieldLogger, fieldType,
+              path, problems).hasInstantiableSubtypes();
         }
       }
     }
@@ -1333,13 +1332,13 @@
    * it is applied to be serializable. As a side effect, populates
    * {@link #typeToTypeInfoComputed} in the same way as
    * {@link #checkTypeInstantiable(TreeLogger, JType, boolean)}.
-   *
+   * 
    * @param logger
    * @param baseType - The generic type the parameter is on
    * @param paramIndex - The index of the parameter in the generic type
    * @param typeArg - An upper bound on the actual argument being applied to the
    *          generic type
-   *
+   * 
    * @return Whether the a parameterized type can be serializable if
    *         <code>baseType</code> is the base type and the
    *         <code>paramIndex</code>th type argument is a subtype of
@@ -1366,13 +1365,12 @@
               paramIndex);
           if (otherFlowInfo.getExposure() >= 0
               && otherFlowInfo.isTransitivelyAffectedBy(flowInfoForArrayParam)) {
-            problems.add(baseType,
-                "Cannot serialize type '"
-                    + baseType.getParameterizedQualifiedSourceName()
-                    + "' when given an argument of type '"
-                    + typeArg.getParameterizedQualifiedSourceName()
-                    + "' because it appears to require serializing arrays "
-                    + "of unbounded dimension", Priority.DEFAULT);
+            problems.add(baseType, "Cannot serialize type '"
+                + baseType.getParameterizedQualifiedSourceName()
+                + "' when given an argument of type '"
+                + typeArg.getParameterizedQualifiedSourceName()
+                + "' because it appears to require serializing arrays "
+                + "of unbounded dimension", Priority.DEFAULT);
             return false;
           }
         }
@@ -1403,7 +1401,8 @@
 
       default: {
         assert (exposure >= TypeParameterExposureComputer.EXPOSURE_MIN_BOUNDED_ARRAY);
-        problems.add(getArrayType(typeOracle, exposure, typeArg),
+        problems.add(
+            getArrayType(typeOracle, exposure, typeArg),
             "Checking type argument "
                 + paramIndex
                 + " of type '"
@@ -1475,9 +1474,9 @@
         JClassType subtype = candidates.get(i);
         String worstMessage = problems.getWorstMessageForType(subtype);
         if (worstMessage == null) {
-          possibilities[i] = "   subtype " + 
-            subtype.getParameterizedQualifiedSourceName() +
-            " is not instantiable";
+          possibilities[i] = "   subtype "
+              + subtype.getParameterizedQualifiedSourceName()
+              + " is not instantiable";
         } else {
           // message with have the form "FQCN some-problem-description"
           possibilities[i] = "   subtype " + worstMessage;
@@ -1500,8 +1499,7 @@
     return tic;
   }
 
-  private boolean isAllowedByFilter(JClassType classType,
-      ProblemReport problems) {
+  private boolean isAllowedByFilter(JClassType classType, ProblemReport problems) {
     return isAllowedByFilter(typeFilter, classType, problems);
   }
 
@@ -1531,12 +1529,10 @@
   }
 
   private void logReachableTypes(TreeLogger logger) {
-    PrintWriter printWriter = null;
-    if (logOutputStream != null) {
+    if (logOutputWriter != null) {
       // Route the TreeLogger output to an output stream.
-      printWriter = new PrintWriter(logOutputStream);
       PrintWriterTreeLogger printWriterTreeLogger = new PrintWriterTreeLogger(
-          printWriter);
+          logOutputWriter);
       printWriterTreeLogger.setMaxDetail(TreeLogger.ALL);
       logger = printWriterTreeLogger;
     }
@@ -1571,8 +1567,8 @@
       logger.log(TreeLogger.DEBUG, "");
     }
 
-    if (printWriter != null) {
-      printWriter.flush();
+    if (logOutputWriter != null) {
+      logOutputWriter.flush();
     }
   }
 
@@ -1596,8 +1592,8 @@
     boolean success = canBeInstantiated(type, problems)
         && shouldConsiderFieldsForSerialization(type, problems);
     if (success) {
-      logger.log(TreeLogger.DEBUG,
-          type.getParameterizedQualifiedSourceName() + " might be instantiable");
+      logger.log(TreeLogger.DEBUG, type.getParameterizedQualifiedSourceName()
+          + " might be instantiable");
     }
     return success;
   }
@@ -1610,7 +1606,7 @@
   /**
    * Remove serializable types that were visited due to speculative paths but
    * are not really needed for serialization.
-   *
+   * 
    * NOTE: This is currently much more limited than it should be. For example, a
    * path sensitive prune could remove instantiable types also.
    */
diff --git a/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml b/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml
index c015e0c..7350e5c 100644
--- a/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml
+++ b/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml
@@ -12,8 +12,7 @@
 <!-- implied. License for the specific language governing permissions and   -->
 <!-- limitations under the License.                                         -->
 
-<!-- This is an abstract module.  Specific linker tests should inherit -->
-<!-- from this module and specify a linker.  -->
+<!-- Module for linker tests -->
 
 <module>
 	<inherits name='com.google.gwt.junit.JUnit' />
diff --git a/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureIFrameLinker.java b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureIFrameLinker.java
index 33c5acc..56b3222 100644
--- a/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureIFrameLinker.java
+++ b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureIFrameLinker.java
@@ -15,15 +15,14 @@
  */
 package com.google.gwt.user.server.runasync;
 
-import com.google.gwt.core.ext.linker.LinkerOrder;
-import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.linker.IFrameLinker;
 
 /**
  * Load modules from a custom servlet in order to test download failure
  * behavior.
  */
-@LinkerOrder(Order.PRE)
+@Shardable
 public class RunAsyncFailureIFrameLinker extends IFrameLinker {
 
   @Override