Merge SOYC report branch into trunk.

Patch by: bobv, kprobst
Review by: kprobst, spoon, bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4218 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java
index f2b3cd3..a45e34c 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java
@@ -38,7 +38,7 @@
    */
   protected final SyntheticArtifact emitBytes(TreeLogger logger, byte[] what,
       String partialPath) throws UnableToCompleteException {
-    return new SyntheticArtifact(getClass(), partialPath, what);
+    return new SyntheticArtifact(logger, getClass(), partialPath, what);
   }
 
   /**
@@ -54,7 +54,8 @@
       InputStream what, String partialPath) throws UnableToCompleteException {
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     Util.copy(logger, what, out);
-    return new SyntheticArtifact(getClass(), partialPath, out.toByteArray());
+    return new SyntheticArtifact(logger, getClass(), partialPath,
+        out.toByteArray());
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/CompilationAnalysis.java b/dev/core/src/com/google/gwt/core/ext/linker/CompilationAnalysis.java
new file mode 100644
index 0000000..b5b75d2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/CompilationAnalysis.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.linker;
+
+import com.google.gwt.core.ext.Linker;
+import com.google.gwt.core.ext.soyc.ClassMember;
+import com.google.gwt.core.ext.soyc.FunctionMember;
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.core.ext.soyc.Story;
+
+import java.util.SortedSet;
+
+/**
+ * Represents analysis data for a CompilationResult.
+ */
+public abstract class CompilationAnalysis extends Artifact<CompilationAnalysis> {
+
+  /**
+   * Associates a Story and a Range of the output. Instances of this interface
+   * are obtained from {@link CompilationAnalysis#getSnippets()}.
+   */
+  public interface Snippet {
+    Range getRange();
+
+    Story getStory();
+  }
+
+  protected CompilationAnalysis(Class<? extends Linker> linkerType) {
+    super(linkerType);
+  }
+
+  /**
+   * Returns all ClassMembers present in the CompilationResult. This method
+   * would typically be used by consumers that are interested in the type
+   * hierarchy of the compilation.
+   */
+  public abstract SortedSet<ClassMember> getClasses();
+
+  /**
+   * Returns the CompilationResult upon which the analysis was performed.
+   */
+  public abstract CompilationResult getCompilationResult();
+
+  /**
+   * Returns all JavaScript FunctionMembers in the output.
+   */
+  public abstract SortedSet<FunctionMember> getFunctions();
+
+  /**
+   * Provides access to the assignments of Stories to Ranges for a fragment of
+   * the output. The Ranges are guaranteed not to overlap, and may be used for
+   * exact accounting of bytes. Due to the potential for very large data-sets to
+   * be accessible through this method, it is recommended that Snippets should
+   * be processed in an incremental fashion that does not require all instances
+   * to be retained at once.
+   */
+  /*
+   * NB: The reason that this returns an Iterable, and not a Map, is that we
+   * want to delay the construction of Range objects for as long as possible. If
+   * we were to return a Map for an analysis of N stories, we would also need N
+   * Ranges, plus the overhead of constructing an ordered Map.
+   */
+  public abstract Iterable<Snippet> getSnippets(int fragmentNumber);
+
+  /**
+   * Returns all Stories.
+   */
+  public abstract SortedSet<Story> getStories();
+
+  @Override
+  public final int hashCode() {
+    // NB: Identity is keyed to the CompilationResult
+    return getCompilationResult().hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "Compilation analysis for " + getCompilationResult().toString();
+  }
+
+  @Override
+  protected final int compareToComparableArtifact(CompilationAnalysis o) {
+    /*
+     * The identity of a CompilationAnalysis is based on the identity of its
+     * associated CompilationResult.
+     */
+    return getCompilationResult().compareToComparableArtifact(
+        o.getCompilationResult());
+  }
+
+  @Override
+  protected final Class<CompilationAnalysis> getComparableArtifactType() {
+    return CompilationAnalysis.class;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
index 5e93e34..eed6c6a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
@@ -18,26 +18,44 @@
 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.dev.util.Util;
 
-import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 
 /**
  * Artifacts created by {@link AbstractLinker}.
  */
 public class SyntheticArtifact extends EmittedArtifact {
-  private final byte[] data;
+  private final File backing;
 
-  SyntheticArtifact(Class<? extends Linker> linkerType, String partialPath,
-      byte[] data) {
+  SyntheticArtifact(TreeLogger logger, Class<? extends Linker> linkerType,
+      String partialPath, byte[] data) throws UnableToCompleteException {
     super(linkerType, partialPath);
     assert data != null;
-    this.data = data;
+
+    try {
+      backing = File.createTempFile("synthetic", ".artifact");
+      backing.deleteOnExit();
+      Util.writeBytesToFile(TreeLogger.NULL, backing, data);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to write backing file for artifact "
+          + partialPath, e);
+      throw new UnableToCompleteException();
+    }
   }
 
   @Override
   public InputStream getContents(TreeLogger logger)
       throws UnableToCompleteException {
-    return new ByteArrayInputStream(data);
+    try {
+      return new FileInputStream(backing);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to read backing file for artifact "
+          + getPartialPath(), e);
+      throw new UnableToCompleteException();
+    }
   }
 }
\ 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 3d78cb9..d13c0218 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
@@ -40,7 +40,7 @@
 
 /**
  * A base class for Linkers that use an external script to boostrap the GWT
- * module. This implementation injects JavaScript snippits into a JS program
+ * module. This implementation injects JavaScript Snippets into a JS program
  * defined in an external file.
  */
 public abstract class SelectionScriptLinker extends AbstractLinker {
@@ -295,7 +295,7 @@
   }
 
   /**
-   * Generate a snippit of JavaScript to inject an external stylesheet.
+   * Generate a Snippet of JavaScript to inject an external stylesheet.
    * 
    * <pre>
    * if (!__gwt_stylesLoaded['URL']) {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationAnalysis.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationAnalysis.java
new file mode 100644
index 0000000..a1fbf06
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationAnalysis.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.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.CompilationAnalysis;
+import com.google.gwt.core.ext.linker.CompilationResult;
+import com.google.gwt.core.ext.soyc.ClassMember;
+import com.google.gwt.core.ext.soyc.FunctionMember;
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.core.ext.soyc.Story;
+import com.google.gwt.core.ext.soyc.Story.Origin;
+import com.google.gwt.core.ext.soyc.impl.AbstractMemberWithDependencies;
+import com.google.gwt.core.ext.soyc.impl.MemberFactory;
+import com.google.gwt.core.ext.soyc.impl.OriginImpl;
+import com.google.gwt.core.ext.soyc.impl.SnippetIterator;
+import com.google.gwt.core.ext.soyc.impl.StandardClassMember;
+import com.google.gwt.core.ext.soyc.impl.StandardFieldMember;
+import com.google.gwt.core.ext.soyc.impl.StandardFunctionMember;
+import com.google.gwt.core.ext.soyc.impl.StandardMethodMember;
+import com.google.gwt.core.ext.soyc.impl.StoryImpl;
+import com.google.gwt.dev.jjs.Correlation;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.Correlation.Axis;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.js.ast.JsFunction;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.Stack;
+import java.util.TreeSet;
+
+/**
+ * An implementation of CompilationAnalysis. This class transforms SourceInfos
+ * and related data into an API suitable for public consumption via the Linker
+ * API.
+ */
+public class StandardCompilationAnalysis extends CompilationAnalysis {
+  /**
+   * A roll-up struct for all the data produced by the analysis to make
+   * serialization simpler.
+   */
+  private static class Data implements Serializable {
+    SortedSet<ClassMember> classes;
+    SortedSet<FunctionMember> functions;
+
+    /**
+     * These are the Stories in the order in which they should be presented to
+     * the user via {@link CompilationAnalysis#getSnippets()}.
+     */
+    Map<Integer, List<StoryImpl>> orderedStories = new HashMap<Integer, List<StoryImpl>>();
+
+    SortedSet<Story> stories;
+  }
+
+  /**
+   * Associates a SourceInfo with a Range.
+   */
+  private static class RangeInfo {
+    public final SourceInfo info;
+    public final Range range;
+
+    public RangeInfo(Range range, SourceInfo info) {
+      this.range = range;
+      this.info = info;
+    }
+  }
+
+  private Data data;
+
+  /**
+   * Used by {@link #popAndRecord(Stack)} to determine start and end ranges.
+   */
+  private int lastEnd = 0;
+
+  private CompilationResult result;
+
+  /**
+   * This is a class field for convenience, but it should be deleted at the end
+   * of the constructor.
+   */
+  private transient Map<SourceInfo, StoryImpl> storyCache = new IdentityHashMap<SourceInfo, StoryImpl>();
+
+  /**
+   * This is a class field for convenience, but it should be deleted at the end
+   * of the constructor.
+   */
+  private transient Map<Correlation, Member> membersByCorrelation = new IdentityHashMap<Correlation, Member>();
+
+  /**
+   * Constructed by PermutationCompiler.
+   */
+  public StandardCompilationAnalysis(TreeLogger logger,
+      List<Map<Range, SourceInfo>> sourceInfoMaps)
+      throws UnableToCompleteException {
+    super(StandardLinkerContext.class);
+    logger = logger.branch(TreeLogger.INFO,
+        "Creating CompilationAnalysis (this may take some time)");
+
+    data = new Data();
+
+    /*
+     * Don't retain beyond the constructor to avoid lingering references to AST
+     * nodes.
+     */
+    MemberFactory memberFactory = new MemberFactory();
+
+    // Record what we've seen so far
+    TreeSet<ClassMember> classesMutable = new TreeSet<ClassMember>(
+        Member.SOURCE_NAME_COMPARATOR);
+    TreeSet<FunctionMember> functionsMutable = new TreeSet<FunctionMember>(
+        Member.SOURCE_NAME_COMPARATOR);
+    Set<SourceInfo> sourceInfoSeen = new HashSet<SourceInfo>();
+
+    int fragment = 0;
+    for (Map<Range, SourceInfo> sourceInfoMap : sourceInfoMaps) {
+      lastEnd = 0;
+      analyzeFragment(memberFactory, classesMutable, functionsMutable,
+          sourceInfoMap, sourceInfoSeen, fragment++);
+    }
+
+    data.classes = Collections.unmodifiableSortedSet(classesMutable);
+    data.functions = Collections.unmodifiableSortedSet(functionsMutable);
+
+    // Deduplicate the ordered stories into an ordered set
+    SortedSet<Story> mutableStories = new TreeSet<Story>(
+        StoryImpl.ID_COMPARATOR);
+    for (List<StoryImpl> stories : data.orderedStories.values()) {
+      mutableStories.addAll(stories);
+    }
+    data.stories = Collections.unmodifiableSortedSet(mutableStories);
+
+    /*
+     * Clear the member fields that we don't need anymore to allow GC of the
+     * SourceInfo objects
+     */
+    membersByCorrelation = null;
+    storyCache = null;
+
+    logger.log(TreeLogger.INFO, "Done");
+  }
+
+  @Override
+  public SortedSet<ClassMember> getClasses() {
+    return data.classes;
+  }
+
+  @Override
+  public CompilationResult getCompilationResult() {
+    return result;
+  }
+
+  @Override
+  public SortedSet<FunctionMember> getFunctions() {
+    return data.functions;
+  }
+
+  @Override
+  public Iterable<Snippet> getSnippets(int fragment) {
+    final List<StoryImpl> stories = data.orderedStories.get(fragment);
+    if (stories == null) {
+      throw new IllegalArgumentException("Unknown fragment id " + fragment);
+    }
+
+    return new Iterable<Snippet>() {
+      public Iterator<Snippet> iterator() {
+        assert stories != null;
+        return new SnippetIterator(stories);
+      }
+    };
+  }
+
+  @Override
+  public SortedSet<Story> getStories() {
+    return data.stories;
+  }
+
+  /**
+   * Back-channel setter used by PermutationCompiler.
+   */
+  public void setCompilationResult(CompilationResult result) {
+    this.result = result;
+  }
+
+  private void analyzeFragment(MemberFactory memberFactory,
+      TreeSet<ClassMember> classesMutable,
+      TreeSet<FunctionMember> functionsMutable,
+      Map<Range, SourceInfo> sourceInfoMap, Set<SourceInfo> sourceInfoSeen,
+      int fragment) {
+    /*
+     * We want to iterate over the Ranges so that enclosing Ranges come before
+     * their enclosed Ranges...
+     */
+    Range[] dependencyOrder = sourceInfoMap.keySet().toArray(
+        new Range[sourceInfoMap.size()]);
+    Arrays.sort(dependencyOrder, Range.DEPENDENCY_ORDER_COMPARATOR);
+
+    Stack<RangeInfo> dependencyScope = new Stack<RangeInfo>();
+    for (Range range : dependencyOrder) {
+      SourceInfo info = sourceInfoMap.get(range);
+      assert info != null;
+
+      // Infer dependency information
+      if (!dependencyScope.isEmpty()) {
+
+        /*
+         * Pop frames until we get back to a container, using this as a chance
+         * to build up our list of non-overlapping Ranges to report back to the
+         * user.
+         */
+        while (!dependencyScope.peek().range.contains(range)) {
+          popAndRecord(dependencyScope, fragment);
+        }
+      }
+
+      // Possibly create and record Members
+      if (!sourceInfoSeen.contains(info)) {
+        sourceInfoSeen.add(info);
+        for (Correlation c : info.getPrimaryCorrelations()) {
+          if (membersByCorrelation.containsKey(c)) {
+            continue;
+          }
+
+          switch (c.getAxis()) {
+            case CLASS: {
+              JReferenceType type = c.getType();
+              StandardClassMember member = memberFactory.get(type);
+              membersByCorrelation.put(c, member);
+              classesMutable.add(member);
+              break;
+            }
+            case FIELD: {
+              JField field = c.getField();
+              JReferenceType type = c.getType();
+              StandardFieldMember member = memberFactory.get(field);
+              memberFactory.get(type).addField(member);
+              membersByCorrelation.put(c, member);
+              break;
+            }
+            case FUNCTION: {
+              JsFunction function = c.getFunction();
+              StandardFunctionMember member = memberFactory.get(function);
+              membersByCorrelation.put(c, member);
+              functionsMutable.add(member);
+              break;
+            }
+            case METHOD: {
+              JMethod method = c.getMethod();
+              JReferenceType type = c.getType();
+              StandardMethodMember member = memberFactory.get(method);
+              memberFactory.get(type).addMethod(member);
+              membersByCorrelation.put(c, member);
+              break;
+            }
+          }
+        }
+      }
+
+      /*
+       * Record dependencies as observed in the structure of the JS output. This
+       * an an ad-hoc approach that just looks at which SourceInfos are used
+       * within the Range of another SourceInfo.
+       */
+      Set<Correlation> correlationsInScope = new HashSet<Correlation>();
+      for (RangeInfo outer : dependencyScope) {
+        SourceInfo outerInfo = outer.info;
+        correlationsInScope.addAll(outerInfo.getPrimaryCorrelations());
+
+        for (Correlation outerCorrelation : outerInfo.getPrimaryCorrelations()) {
+          Member outerMember = membersByCorrelation.get(outerCorrelation);
+
+          if (outerMember instanceof AbstractMemberWithDependencies) {
+            for (Correlation innerCorrelation : info.getAllCorrelations()) {
+              /*
+               * This check prevents an inlined method from depending on the
+               * method or function into which is was inlined.
+               */
+              if (correlationsInScope.contains(innerCorrelation)) {
+                continue;
+              }
+
+              Member innerMember = membersByCorrelation.get(innerCorrelation);
+
+              /*
+               * The null check is because we may not create Members for all
+               * types of Correlations.
+               */
+              if (innerMember != null) {
+                if (((AbstractMemberWithDependencies) outerMember).addDependency(innerMember)) {
+                  // System.out.println(outerMember + " -> " + innerMember);
+                }
+              }
+            }
+          }
+        }
+      }
+
+      dependencyScope.push(new RangeInfo(range, info));
+    }
+
+    // Unwind the rest of the stack to finish out the ranges
+    while (!dependencyScope.isEmpty()) {
+      popAndRecord(dependencyScope, fragment);
+    }
+
+    /*
+     * Because the first Range corresponds to the SourceInfo of the whole
+     * program, we'll know that we got all of the data if the ends match up. If
+     * this assert passes, we know that we've correctly generated a sequence of
+     * non-overlapping Ranges that encompass the whole program.
+     */
+    assert dependencyOrder[0].getEnd() == lastEnd;
+  }
+
+  /**
+   * Remove an element from the RangeInfo stack and stare a new StoryImpl with
+   * the right length, possibly sub-dividing the super-enclosing Range in the
+   * process.
+   */
+  private void popAndRecord(Stack<RangeInfo> dependencyScope, int fragment) {
+    RangeInfo rangeInfo = dependencyScope.pop();
+    Range toStore = rangeInfo.range;
+
+    /*
+     * Make a new Range for the gap between the popped Range and whatever we
+     * last stored.
+     */
+    if (lastEnd < toStore.getStart()) {
+      Range newRange = new Range(lastEnd, toStore.getStart());
+      assert !dependencyScope.isEmpty();
+
+      SourceInfo gapInfo = dependencyScope.peek().info;
+      recordStory(gapInfo, fragment, newRange.length());
+      lastEnd += newRange.length();
+    }
+
+    /*
+     * Store as much of the current Range as we haven't previously stored. The
+     * Max.max() is there to take care of the tail end of Ranges that have had a
+     * sub-range previously stored.
+     */
+    if (lastEnd < toStore.getEnd()) {
+      Range newRange = new Range(Math.max(lastEnd, toStore.getStart()),
+          toStore.getEnd());
+      recordStory(rangeInfo.info, fragment, newRange.length());
+      lastEnd += newRange.length();
+    }
+  }
+
+  private void recordStory(SourceInfo info, int fragment, int length) {
+    assert storyCache != null;
+
+    StoryImpl theStory;
+    if (!storyCache.containsKey(info)) {
+
+      SortedSet<Member> members = new TreeSet<Member>(
+          Member.TYPE_AND_SOURCE_NAME_COMPARATOR);
+
+      if (info != null) {
+        for (Correlation c : info.getAllCorrelations()) {
+          Member m = membersByCorrelation.get(c);
+          if (m != null) {
+            members.add(m);
+          }
+        }
+      }
+
+      SortedSet<Origin> origins = new TreeSet<Origin>();
+      for (Correlation c : info.getAllCorrelations(Axis.ORIGIN)) {
+        origins.add(new OriginImpl(c.getOrigin()));
+      }
+
+      String literalType = null;
+      Correlation literalCorrelation = info.getPrimaryCorrelation(Axis.LITERAL);
+      if (literalCorrelation != null) {
+        literalType = literalCorrelation.getLiteral().getDescription();
+      }
+
+      theStory = new StoryImpl(storyCache.size(), members, info.getMutations(),
+          origins, literalType, fragment, length);
+      storyCache.put(info, theStory);
+    } else {
+      // Use a copy-constructed instance
+      theStory = new StoryImpl(storyCache.get(info), length);
+    }
+
+    List<StoryImpl> stories = data.orderedStories.get(fragment);
+    if (stories == null) {
+      stories = new ArrayList<StoryImpl>();
+      data.orderedStories.put(fragment, stories);
+    }
+    stories.add(theStory);
+  }
+}
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 716995e..bf4306b 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
@@ -15,12 +15,13 @@
  */
 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.CompilationResult;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.dev.PermutationResult;
-import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.FileBackedObject;
 
-import java.io.File;
 import java.io.Serializable;
 import java.lang.ref.SoftReference;
 import java.util.Collections;
@@ -69,14 +70,15 @@
    */
   public static final Comparator<SortedMap<SelectionProperty, String>> MAP_COMPARATOR = new MapComparator();
 
-  private final File resultFile;
+  private final FileBackedObject<PermutationResult> resultFile;
   private transient SoftReference<String[]> js;
 
   private final SortedSet<SortedMap<SelectionProperty, String>> propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>(
       MAP_COMPARATOR);
   private final String strongName;
 
-  public StandardCompilationResult(String[] js, String strongName, File resultFile) {
+  public StandardCompilationResult(String[] js, String strongName,
+      FileBackedObject<PermutationResult> resultFile) {
     super(StandardLinkerContext.class);
     this.js = new SoftReference<String[]>(js);
     this.strongName = strongName;
@@ -104,19 +106,13 @@
     if (toReturn == null) {
       PermutationResult result;
       try {
-        result = Util.readFileAsObject(resultFile, PermutationResult.class);
-      } catch (ClassNotFoundException e) {
+        result = resultFile.newInstance(TreeLogger.NULL);
+        toReturn = result.getJs();
+        js = new SoftReference<String[]>(toReturn);
+      } catch (UnableToCompleteException e) {
         throw new RuntimeException(
-            "Unexpectedly unable to read PermutationResult '"
-                + resultFile.getAbsolutePath() + "'", e);
+            "Unexpectedly unable to read PermutationResult");
       }
-      if (result == null) {
-        throw new RuntimeException(
-            "Unexpectedly unable to read PermutationResult '"
-                + resultFile.getAbsolutePath() + "'");
-      }
-      toReturn = result.getJs();
-      js = new SoftReference<String[]>(toReturn);
     }
     return toReturn;
   }
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 cb9619a..1a4e649 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
@@ -51,6 +51,7 @@
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsScope;
 import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.Util;
 
 import java.io.File;
@@ -263,20 +264,9 @@
    * Gets or creates a CompilationResult for the given JavaScript program.
    */
   public StandardCompilationResult getCompilation(TreeLogger logger,
-      File resultFile) throws UnableToCompleteException {
-    PermutationResult permutationResult;
-    try {
-      permutationResult = Util.readFileAsObject(resultFile,
-          PermutationResult.class);
-    } catch (ClassNotFoundException e) {
-      logger.log(TreeLogger.ERROR, "Unable to instantiate PermutationResult", e);
-      throw new UnableToCompleteException();
-    }
-
-    if (permutationResult == null) {
-      logger.log(TreeLogger.ERROR, "Unable to read PermutationResult");
-      throw new UnableToCompleteException();
-    }
+      FileBackedObject<PermutationResult> resultFile)
+      throws UnableToCompleteException {
+    PermutationResult permutationResult = resultFile.newInstance(logger);
 
     String strongName = Util.computeStrongName(Util.getBytes(permutationResult.getJs()));
     StandardCompilationResult result = resultsByStrongName.get(strongName);
@@ -285,6 +275,16 @@
           strongName, resultFile);
       resultsByStrongName.put(result.getStrongName(), result);
       artifacts.add(result);
+
+      // Add any other Permutations
+      ArtifactSet otherArtifacts = permutationResult.getArtifacts();
+      if (otherArtifacts != null) {
+        // Fixups for StandardCompilationAnalysis objects
+        for (StandardCompilationAnalysis a : otherArtifacts.find(StandardCompilationAnalysis.class)) {
+          a.setCompilationResult(result);
+        }
+        artifacts.addAll(otherArtifacts);
+      }
     }
     return result;
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/ClassMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/ClassMember.java
new file mode 100644
index 0000000..e8e84b8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/ClassMember.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.util.SortedSet;
+
+/**
+ * Represents a reference type, such as a class or interface, in the compiled
+ * output. Methods and fields of the original Java type will have been pruned by
+ * the compiler, so the values returned by {@link #getFields()} and
+ * {@link #getMethods()} may be incomplete when compared to the original Java
+ * type.
+ */
+public interface ClassMember extends HasDependencies,
+    HasOverrides<ClassMember>, Member {
+  /**
+   * Returns the fields of the ClassMember that have been retained in the
+   * compiled output.
+   */
+  SortedSet<FieldMember> getFields();
+
+  /**
+   * Returns the methods of the ClassMember that have been retained in the
+   * compiled output.
+   */
+  SortedSet<MethodMember> getMethods();
+
+  /**
+   * Returns the Java package from which the ClassMember originated.
+   */
+  String getPackage();
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/FieldMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/FieldMember.java
new file mode 100644
index 0000000..a431f86
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/FieldMember.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc;
+
+/**
+ * Represents a field in a Java type.
+ */
+public interface FieldMember extends HasEnclosing, Member {
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/FunctionMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/FunctionMember.java
new file mode 100644
index 0000000..574a292
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/FunctionMember.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/**
+ * 
+ */
+package com.google.gwt.core.ext.soyc;
+
+/**
+ * Represents a JavaScript function in the compiled output. Due to compiler
+ * optimizations, there may be arbitrary relationships between FunctionMembers
+ * and MethodMembers.
+ */
+public interface FunctionMember extends HasDependencies, Member {
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/HasAliases.java b/dev/core/src/com/google/gwt/core/ext/soyc/HasAliases.java
new file mode 100644
index 0000000..354aff4
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/HasAliases.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.util.SortedSet;
+
+/**
+ * A tag interface for Members to indicate that there may be additional,
+ * possibly non-unique, names in the compiled output that refer to the member.
+ */
+public interface HasAliases {
+  /**
+   * Returns any aliases used in the compiled output that refer to this
+   * MethodMember. Note that especially in the case of
+   * polymorphically-dispatched methods, there may be many MethodMembers with
+   * overlapping aliases.
+   */
+  SortedSet<String> getJsAliases();
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/HasDependencies.java b/dev/core/src/com/google/gwt/core/ext/soyc/HasDependencies.java
new file mode 100644
index 0000000..637dc52
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/HasDependencies.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/**
+ * 
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.util.SortedSet;
+
+/**
+ * A tag interface for Members to indicate that they have a static dependency on
+ * another Member.
+ */
+public interface HasDependencies {
+  SortedSet<Member> getDependencies();
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/HasEnclosing.java b/dev/core/src/com/google/gwt/core/ext/soyc/HasEnclosing.java
new file mode 100644
index 0000000..d6e1589
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/HasEnclosing.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/**
+ * 
+ */
+package com.google.gwt.core.ext.soyc;
+
+/**
+ * A tag interface for Members to indicate that they have an enclosing
+ * ClassMember.
+ */
+public interface HasEnclosing {
+  ClassMember getEnclosing();
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/HasOverrides.java b/dev/core/src/com/google/gwt/core/ext/soyc/HasOverrides.java
new file mode 100644
index 0000000..5d6001b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/HasOverrides.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/**
+ * 
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.util.SortedSet;
+
+/**
+ * A tag interface for Members to indicate that they override or extend another
+ * Member.
+ * 
+ * @param <T> the type of Member that is overridden or extended
+ */
+public interface HasOverrides<T extends Member> {
+  /**
+   * Returns the complete set of overridden members. For example, if
+   * <code>C</code> extends <code>B</code> extends <code>A</code>, then
+   * <code>C</code> would return the set <code>[A, B]</code>.
+   */
+  SortedSet<T> getOverrides();
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/Member.java b/dev/core/src/com/google/gwt/core/ext/soyc/Member.java
new file mode 100644
index 0000000..8a2b64e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/Member.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * The Member type hierarchy represents structural or logical structures in the
+ * compiled output. Members don't have a getRange() function because the bytes
+ * derived from the member are likely disjoint.
+ */
+public interface Member extends Serializable {
+
+  /**
+   * Compares Members based solely on source name. This comparator is faster
+   * than {@link #TYPE_AND_SOURCE_NAME_COMPARATOR}, but is only appropriate for
+   * use with homogeneous collections of Members.
+   */
+  Comparator<Member> SOURCE_NAME_COMPARATOR = new SourceNameComparator();
+
+  /**
+   * Compares Members based on type and source name.
+   */
+  Comparator<Member> TYPE_AND_SOURCE_NAME_COMPARATOR = new TypeAndSourceNameComparator();;
+
+  /**
+   * Returns the (possibly obfuscated) identifier used in the output.
+   */
+  String getJsName();
+
+  /**
+   * Returns a description of where the source for the Member originated.
+   * Usually, but not always, a URL.
+   */
+  String getSourceLocation();
+
+  /**
+   * Returns the name of the Member in the original source code.
+   */
+  String getSourceName();
+
+  /**
+   * Returns the Member if it is a ClassMember or <code>null</code>.
+   */
+  ClassMember isClass();
+
+  /**
+   * Returns the Member if it is a FieldMember or <code>null</code>.
+   */
+  FieldMember isField();
+
+  /**
+   * Returns the Member if it is a FunctionMember or <code>null</code>.
+   */
+  FunctionMember isFunction();
+
+  /**
+   * Returns the Member if it is a MethodMember or <code>null</code>.
+   */
+  MethodMember isMethod();
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/MethodMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/MethodMember.java
new file mode 100644
index 0000000..9966890
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/MethodMember.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/**
+ * 
+ */
+package com.google.gwt.core.ext.soyc;
+
+/**
+ * Represents compiled JS code derived from a Java method.
+ */
+public interface MethodMember extends HasAliases, HasDependencies,
+    HasEnclosing, HasOverrides<MethodMember>, Member {
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/Range.java b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
new file mode 100644
index 0000000..8414263
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/**
+ * 
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.util.Comparator;
+
+/**
+ * Represents a contiguous region of characters in the compiler output.
+ */
+public final class Range {
+  /**
+   * Sorts Ranges so that a Range will be preceeded by any Ranges that enclose
+   * it.
+   */
+  public static final Comparator<Range> DEPENDENCY_ORDER_COMPARATOR = new Comparator<Range>() {
+    public int compare(Range o1, Range o2) {
+      int a = o1.start - o2.start;
+      if (a != 0) {
+        return a;
+      }
+
+      return o2.end - o1.end;
+    }
+  };
+
+  /**
+   * Sorts Ranges into the order in which they would appear in the source code
+   * based on start position and end position.
+   */
+  public static final Comparator<Range> SOURCE_ORDER_COMPARATOR = new Comparator<Range>() {
+    public int compare(Range o1, Range o2) {
+      int a = o1.start - o2.start;
+      if (a != 0) {
+        return a;
+      }
+
+      return o1.end - o2.end;
+    }
+  };
+
+  private final int end;
+  private final int start;
+
+  /**
+   * Constructor.
+   * 
+   * @param start must be non-negative
+   * @param end must be greater than or equal to <code>start</code>
+   */
+  public Range(int start, int end) {
+    assert start >= 0;
+    assert start <= end;
+    this.start = start;
+    this.end = end;
+  }
+
+  /**
+   * Return <code>true</code> if the given Range lies wholly within the Range.
+   */
+  public boolean contains(Range o) {
+    return start <= o.start && o.end <= end;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof Range)) {
+      return false;
+    }
+    Range o = (Range) obj;
+    return start == o.start && end == o.end;
+  }
+
+  public int getEnd() {
+    return end;
+  }
+
+  public int getStart() {
+    return start;
+  }
+
+  @Override
+  public int hashCode() {
+    return 37 * start + end;
+  }
+
+  public int length() {
+    return end - start;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return "[" + start + " - " + end + ")";
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/SourceNameComparator.java b/dev/core/src/com/google/gwt/core/ext/soyc/SourceNameComparator.java
new file mode 100644
index 0000000..19cd368
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/SourceNameComparator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * This is a top-level type so that we can serialize any TreeMaps that happen to
+ * use {@link Member#SOURCE_NAME_COMPARATOR}.
+ */
+class SourceNameComparator implements Comparator<Member>, Serializable {
+  public int compare(Member o1, Member o2) {
+    return o1.getSourceName().compareTo(o2.getSourceName());
+  }
+
+  /**
+   * Always use the singleton instance.
+   */
+  private Object readResolve() throws ObjectStreamException {
+    return Member.SOURCE_NAME_COMPARATOR;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/Story.java b/dev/core/src/com/google/gwt/core/ext/soyc/Story.java
new file mode 100644
index 0000000..a08fd85
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/Story.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Represents a (possibly disjoint) region of the JavaScript output for which
+ * metadata is available.
+ */
+public interface Story extends Serializable {
+  /*
+   * Corresponds to a SourceInfo.
+   */
+
+  /**
+   * Describes the physical or virtual source location from which a Story
+   * originated.
+   */
+  public interface Origin extends Serializable {
+    /*
+     * Corresponds to a SourceOrigin.
+     */
+
+    /**
+     * Returns the line number, or <code>0</code> if there is no physical
+     * location.
+     */
+    int getLineNumber();
+
+    /**
+     * This is usually, but not always, a URL. If it is not a URL, it will
+     * typically be a Java class name.
+     */
+    String getLocation();
+  }
+
+  /**
+   * If the Story represents a literal value, this method will return a
+   * description of the type of literal. If the Story does not represent a
+   * literal, this method will return <code>null</code>.
+   */
+  // TODO: Consider promoting the Correlation.Literal enum to a public API
+  String getLiteralTypeName();
+
+  /**
+   * Gets the Members of the compilation that the Story is about.
+   */
+  SortedSet<Member> getMembers();
+
+  /**
+   * Gets a list of transformations applied to the original source code in order
+   * to produce the story entry. This method will not return any data unless the
+   * compiler has been configured to collect mutation data.
+   */
+  List<String> getMutations();
+
+  /**
+   * Returns the locations of the Story's source. Identical structures (such as
+   * string literals) that appear in multiple locations in the source may be
+   * merged by the compiler into a single Story.
+   */
+  SortedSet<Origin> getSourceOrigin();
+
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/TypeAndSourceNameComparator.java b/dev/core/src/com/google/gwt/core/ext/soyc/TypeAndSourceNameComparator.java
new file mode 100644
index 0000000..a78746d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/TypeAndSourceNameComparator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * This is a top-level type so that we can serialize any TreeMaps that happen to
+ * use {@link Member#TYPE_AND_SOURCE_NAME_COMPARATOR}.
+ */
+class TypeAndSourceNameComparator implements Comparator<Member>, Serializable {
+  public int compare(Member o1, Member o2) {
+    int r = o1.getClass().getName().compareTo(o2.getClass().getName());
+    if (r != 0) {
+      return r;
+    }
+
+    return o1.getSourceName().compareTo(o2.getSourceName());
+  }
+
+  /**
+   * Always use the singleton instance.
+   */
+  private Object readResolve() throws ObjectStreamException {
+    return Member.TYPE_AND_SOURCE_NAME_COMPARATOR;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/AbstractMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/AbstractMember.java
new file mode 100644
index 0000000..136dc93
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/AbstractMember.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.ClassMember;
+import com.google.gwt.core.ext.soyc.FieldMember;
+import com.google.gwt.core.ext.soyc.FunctionMember;
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.core.ext.soyc.MethodMember;
+import com.google.gwt.dev.jjs.Correlation;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.Correlation.Axis;
+
+/**
+ * Provides implementation of common Member functions.
+ */
+public abstract class AbstractMember implements Member {
+  private final String jsName;
+  private final String sourceLocation;
+
+  public AbstractMember(SourceInfo info) {
+    Correlation nameCorrelation = info.getPrimaryCorrelation(Axis.JS_NAME);
+    if (nameCorrelation != null) {
+      jsName = nameCorrelation.getName().getShortIdent();
+    } else {
+      jsName = null;
+    }
+    sourceLocation = info.getFileName();
+  }
+
+  public String getJsName() {
+    return jsName;
+  }
+
+  public String getSourceLocation() {
+    return sourceLocation;
+  }
+
+  public abstract String getSourceName();
+
+  public ClassMember isClass() {
+    return this instanceof ClassMember ? (ClassMember) this : null;
+  }
+
+  public FieldMember isField() {
+    return this instanceof FieldMember ? (FieldMember) this : null;
+  }
+
+  public FunctionMember isFunction() {
+    return this instanceof FunctionMember ? (FunctionMember) this : null;
+  }
+
+  public MethodMember isMethod() {
+    return this instanceof MethodMember ? (MethodMember) this : null;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/AbstractMemberWithDependencies.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/AbstractMemberWithDependencies.java
new file mode 100644
index 0000000..44fb5cf
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/AbstractMemberWithDependencies.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.HasDependencies;
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.dev.jjs.SourceInfo;
+
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Provides a common implementation of HasDependencies.
+ */
+public abstract class AbstractMemberWithDependencies extends AbstractMember
+    implements HasDependencies {
+  private final SortedSet<Member> dependencies = new TreeSet<Member>(
+      Member.TYPE_AND_SOURCE_NAME_COMPARATOR);
+  private final SortedSet<Member> dependenciesView = Collections.unmodifiableSortedSet(dependencies);
+
+  protected AbstractMemberWithDependencies(SourceInfo info) {
+    super(info);
+  }
+
+  /**
+   * Add a dependency.
+   * 
+   * @return <code>true</code> if the dependency was not previously added.
+   */
+  public boolean addDependency(Member member) {
+    return dependencies.add(member);
+  }
+
+  public SortedSet<Member> getDependencies() {
+    return dependenciesView;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/MemberFactory.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/MemberFactory.java
new file mode 100644
index 0000000..273aa6b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/MemberFactory.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.js.ast.JsFunction;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * A factory object for the standard implementations of Member subtypes. The
+ * factory methods in this type provide canonicalized instances. The maps used
+ * by MemberFactory use hard, identity-based references.
+ */
+public class MemberFactory {
+  private final Map<Class<?>, Map<?, ?>> map = new IdentityHashMap<Class<?>, Map<?, ?>>();
+
+  public StandardFieldMember get(JField field) {
+    return getOrCreate(field, StandardFieldMember.class, JField.class);
+  }
+
+  public StandardMethodMember get(JMethod method) {
+    return getOrCreate(method, StandardMethodMember.class, JMethod.class);
+  }
+
+  public StandardClassMember get(JReferenceType type) {
+    return getOrCreate(type, StandardClassMember.class, JReferenceType.class);
+  }
+
+  public StandardFunctionMember get(JsFunction function) {
+    return getOrCreate(function, StandardFunctionMember.class, JsFunction.class);
+  }
+
+  @SuppressWarnings("unchecked")
+  private <K, V extends Member> Map<K, V> getElementMap(K key, Class<V> clazz) {
+    Map<K, V> elementMap = (Map<K, V>) map.get(clazz);
+    if (elementMap == null) {
+      elementMap = new IdentityHashMap<K, V>();
+      map.put(clazz, elementMap);
+    }
+    return elementMap;
+  }
+
+  /**
+   * Assumes that the implementation of Member has a two-arg constructor that
+   * accepts a MemberFactory and the key.
+   * 
+   * @param <K> the type of key used to canonicalize the mapping
+   * @param <V> the type of Member implementation to use
+   * @param key the key by which the value should be canonicalized
+   * @param implClazz the concrete type of Member to construct
+   * @param constructorParam the declared type of the second parameter of the
+   *          concrete Member type
+   * @return the canonicalized instance of Member for the given key
+   */
+  private <K, V extends Member> V getOrCreate(K key, Class<V> implClazz,
+      Class<? super K> constructorParam) {
+    Map<K, V> elementMap = getElementMap(key, implClazz);
+
+    V toReturn = elementMap.get(key);
+    if (toReturn == null) {
+      try {
+        Constructor<V> ctor = implClazz.getConstructor(MemberFactory.class,
+            constructorParam);
+        toReturn = ctor.newInstance(this, key);
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(implClazz.getName()
+            + " must declare a two-arg (MemberFactory, "
+            + constructorParam.getName() + ") constructor", e);
+      } catch (IllegalArgumentException e) {
+        // Error on the part of this type
+        throw new RuntimeException(e);
+      } catch (InstantiationException e) {
+        // Error on the part of this type, asking for a non-instantiable type
+        throw new RuntimeException(e);
+      } catch (IllegalAccessException e) {
+        // Error on the part of the coder of implClazz
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        // Probably a RuntimeException thrown from the constructor
+        if (e.getCause() instanceof RuntimeException) {
+          throw (RuntimeException) e.getCause();
+        }
+        throw new RuntimeException(e);
+      }
+
+      elementMap.put(key, toReturn);
+    }
+
+    return toReturn;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/OriginImpl.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/OriginImpl.java
new file mode 100644
index 0000000..76a56fe
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/OriginImpl.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.Story.Origin;
+import com.google.gwt.dev.jjs.SourceOrigin;
+
+/**
+ * An implementation of Origin, that initializes itself from a SourceOrigin.
+ */
+public class OriginImpl implements Origin, Comparable<OriginImpl> {
+
+  private final int lineNum;
+  private final String location;
+
+  public OriginImpl(SourceOrigin origin) {
+    this.location = origin.getFileName();
+    this.lineNum = origin.getStartLine();
+  }
+
+  public int compareTo(OriginImpl o) {
+    int a = location.compareTo(o.location);
+    if (a != 0) {
+      return a;
+    }
+    return lineNum - o.lineNum;
+  }
+
+  public int getLineNumber() {
+    return lineNum;
+  }
+
+  public String getLocation() {
+    return location;
+  }
+
+  @Override
+  public String toString() {
+    return location + " : " + lineNum;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SnippetIterator.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SnippetIterator.java
new file mode 100644
index 0000000..7d2fb50
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SnippetIterator.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.linker.CompilationAnalysis.Snippet;
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.core.ext.soyc.Story;
+
+import java.util.Iterator;
+
+/**
+ * Uses a list of StoryImpls present a sequence of Snippets by synthesizing
+ * Range objects based on the length of the StoryImpls.
+ */
+public class SnippetIterator implements Iterator<Snippet> {
+  /**
+   * An Iterator over the backing object.
+   */
+  private final Iterator<StoryImpl> iter;
+
+  /**
+   * The starting position for the next Range object generated.
+   */
+  private int start = 0;
+
+  public SnippetIterator(Iterable<StoryImpl> stories) {
+    iter = stories.iterator();
+  }
+
+  public boolean hasNext() {
+    return iter.hasNext();
+  }
+
+  public Snippet next() {
+    final StoryImpl story = iter.next();
+    final Range range = new Range(start, start + story.getLength());
+    start += story.getLength();
+
+    return new Snippet() {
+      public Range getRange() {
+        return range;
+      }
+
+      public Story getStory() {
+        return story;
+      }
+    };
+  }
+
+  public void remove() {
+    throw new UnsupportedOperationException();
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardClassMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardClassMember.java
new file mode 100644
index 0000000..8a0a46f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardClassMember.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.ClassMember;
+import com.google.gwt.core.ext.soyc.FieldMember;
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.core.ext.soyc.MethodMember;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * An implementation of ClassMember. This implementation always returns
+ * unmodifiable collections since it is exposed directly to user code via the
+ * Linker API.
+ */
+public class StandardClassMember extends AbstractMemberWithDependencies
+    implements ClassMember {
+  private final SortedSet<FieldMember> fields = new TreeSet<FieldMember>(
+      Member.SOURCE_NAME_COMPARATOR);
+  private final SortedSet<FieldMember> fieldsView = Collections.unmodifiableSortedSet(fields);
+  private final SortedSet<MethodMember> methods = new TreeSet<MethodMember>(
+      Member.SOURCE_NAME_COMPARATOR);
+  private final SortedSet<MethodMember> methodsView = Collections.unmodifiableSortedSet(methods);
+  private final SortedSet<ClassMember> overridesView;
+  private final String packageName;
+  private final String sourceName;
+
+  /**
+   * Constructed by {@link MemberFactory#get(JReferenceType)}.
+   */
+  public StandardClassMember(MemberFactory factory, JReferenceType type) {
+    super(type.getSourceInfo());
+
+    int index = type.getName().lastIndexOf('.');
+    if (index < 0) {
+      packageName = "";
+    } else {
+      packageName = type.getName().substring(0, index).intern();
+    }
+    sourceName = type.getName().intern();
+
+    Set<JReferenceType> seen = new HashSet<JReferenceType>();
+    Set<JReferenceType> toTraverse = new HashSet<JReferenceType>();
+    toTraverse.add(type);
+
+    SortedSet<ClassMember> overrides = new TreeSet<ClassMember>(
+        Member.SOURCE_NAME_COMPARATOR);
+
+    while (!toTraverse.isEmpty()) {
+      JReferenceType currentType = toTraverse.iterator().next();
+      seen.add(currentType);
+
+      if (currentType != type) {
+        overrides.add(factory.get(currentType));
+      }
+
+      if (currentType.extnds != null) {
+        toTraverse.add(currentType.extnds);
+      }
+
+      if (currentType.implments != null) {
+        toTraverse.addAll(currentType.implments);
+      }
+
+      toTraverse.removeAll(seen);
+    }
+    overridesView = Collections.unmodifiableSortedSet(overrides);
+  }
+
+  public void addField(FieldMember field) {
+    fields.add(field);
+  }
+
+  public void addMethod(MethodMember method) {
+    methods.add(method);
+  }
+
+  public SortedSet<FieldMember> getFields() {
+    return fieldsView;
+  }
+
+  public SortedSet<MethodMember> getMethods() {
+    return methodsView;
+  }
+
+  public SortedSet<ClassMember> getOverrides() {
+    return overridesView;
+  }
+
+  public String getPackage() {
+    return packageName;
+  }
+
+  @Override
+  public String getSourceName() {
+    return sourceName;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return "ClassMember " + getSourceName();
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardFieldMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardFieldMember.java
new file mode 100644
index 0000000..e40185c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardFieldMember.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.ClassMember;
+import com.google.gwt.core.ext.soyc.FieldMember;
+import com.google.gwt.dev.jjs.ast.JField;
+
+/**
+ * An implementation of FieldMember.
+ */
+public class StandardFieldMember extends AbstractMember implements FieldMember {
+  private final ClassMember enclosing;
+  private final String sourceName;
+
+  /**
+   * Constructed by {@link MemberFactory#get(JFieldType)}.
+   */
+  public StandardFieldMember(MemberFactory factory, JField field) {
+    super(field.getSourceInfo());
+    this.enclosing = factory.get(field.getEnclosingType());
+    this.sourceName = field.getEnclosingType().getName() + "::"
+        + field.getName();
+  }
+
+  public ClassMember getEnclosing() {
+    return enclosing;
+  }
+
+  @Override
+  public String getSourceName() {
+    return sourceName;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return "FieldMember " + sourceName;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardFunctionMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardFunctionMember.java
new file mode 100644
index 0000000..9b530b7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardFunctionMember.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.FunctionMember;
+import com.google.gwt.dev.js.ast.JsFunction;
+
+/**
+ * An implementation of FunctionMember.
+ */
+public class StandardFunctionMember extends AbstractMemberWithDependencies
+    implements FunctionMember {
+
+  private final String sourceName;
+
+  /**
+   * Constructed by {@link MemberFactory#get(JsFunction)}.
+   */
+  public StandardFunctionMember(MemberFactory factory, JsFunction function) {
+    super(function.getSourceInfo());
+    this.sourceName = function.getName().getIdent();
+  }
+
+  @Override
+  public String getSourceName() {
+    return sourceName;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return "FunctionMember " + sourceName;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardMethodMember.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardMethodMember.java
new file mode 100644
index 0000000..e617acf
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StandardMethodMember.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.ClassMember;
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.core.ext.soyc.MethodMember;
+import com.google.gwt.dev.jjs.Correlation;
+import com.google.gwt.dev.jjs.Correlation.Axis;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JType;
+
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * An implementation of MethodMember.
+ */
+public class StandardMethodMember extends AbstractMemberWithDependencies
+    implements MethodMember {
+  private final SortedSet<String> aliasesView;
+  private final ClassMember enclosing;
+  private final String sourceName;
+  private final SortedSet<MethodMember> overridesView;
+
+  /**
+   * Constructed by {@link MemberFactory#get(JMethod)}.
+   */
+  public StandardMethodMember(MemberFactory factory, JMethod method) {
+    super(method.getSourceInfo());
+    this.enclosing = factory.get(method.getEnclosingType());
+
+    StringBuilder sb = new StringBuilder();
+    sb.append(method.getEnclosingType().getName()).append("::");
+    sb.append(method.getName()).append("(");
+    for (JType type : method.getOriginalParamTypes()) {
+      sb.append(type.getJsniSignatureName());
+    }
+    sb.append(")");
+    this.sourceName = sb.toString();
+
+    SortedSet<String> aliases = new TreeSet<String>();
+    for (Correlation c : method.getSourceInfo().getAllCorrelations(
+        Axis.JS_ALIAS)) {
+      aliases.add(c.getName().getShortIdent());
+    }
+    aliasesView = Collections.unmodifiableSortedSet(aliases);
+
+    SortedSet<MethodMember> overrides = new TreeSet<MethodMember>(
+        Member.SOURCE_NAME_COMPARATOR);
+    for (JMethod override : method.overrides) {
+      overrides.add(factory.get(override));
+    }
+    overridesView = Collections.unmodifiableSortedSet(overrides);
+  }
+
+  public ClassMember getEnclosing() {
+    return enclosing;
+  }
+
+  public SortedSet<String> getJsAliases() {
+    return aliasesView;
+  }
+
+  public SortedSet<MethodMember> getOverrides() {
+    return overridesView;
+  }
+
+  @Override
+  public String getSourceName() {
+    return sourceName;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return "MethodMember " + sourceName;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImpl.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImpl.java
new file mode 100644
index 0000000..7aab089
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImpl.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.core.ext.soyc.Story;
+import com.google.gwt.dev.jjs.SourceInfo.Mutation;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * An implementation of the Story interface. This type has two additional pieces
+ * of information not required by the Story interface. The first is a unique id
+ * number and the second is a length. Instead of storing range objects for each
+ * StoryImpl, we simply store the StoryImpls in order and calculate the Range
+ * for the StoryImpl based on its length.
+ * 
+ * @see SnippetIterator#next()
+ */
+public class StoryImpl implements Story, Serializable {
+  /**
+   * Orders StoryImpl's by their id number.
+   */
+  public static final Comparator<Story> ID_COMPARATOR = new StoryImplComparator();
+
+  private final int id;
+  private final int fragment;
+  private final int length;
+  private final String literalDescription;
+  private final SortedSet<Member> members;
+  private final List<String> mutations;
+  private final SortedSet<Origin> origins;
+
+  /**
+   * Standard constructor. This constructor will create unmodifiable versions of
+   * the collections passed into it.
+   */
+  public StoryImpl(int id, SortedSet<Member> members, List<Mutation> mutations,
+      SortedSet<Origin> origins, String literalDescription, int fragment,
+      int length) {
+    assert members != null;
+    assert mutations != null;
+    assert origins != null;
+    assert fragment >= 0;
+    assert length > 0;
+    // literalDescription may be null
+
+    this.id = id;
+    this.fragment = fragment;
+    this.length = length;
+    this.literalDescription = literalDescription == null ? null
+        : literalDescription.intern();
+    this.members = Collections.unmodifiableSortedSet(members);
+
+    List<String> mutableMutations = new ArrayList<String>(mutations.size());
+    for (Mutation m : mutations) {
+      mutableMutations.add(m.getDescription() + " by " + m.getCaller());
+    }
+    this.mutations = Collections.unmodifiableList(mutableMutations);
+    this.origins = Collections.unmodifiableSortedSet(origins);
+  }
+
+  /**
+   * This is a copy-constructor that's used when we subdivide a Range. All we
+   * really care about in the shadow version is having a different length; all
+   * of the other fields are initialized from the original.
+   */
+  public StoryImpl(StoryImpl other, int length) {
+    this.id = other.id;
+    this.fragment = other.fragment;
+    this.length = length;
+    this.literalDescription = other.literalDescription;
+    this.members = other.members;
+    this.mutations = other.mutations;
+    this.origins = other.origins;
+  }
+
+  /**
+   * Identity is based on the <code>id</code> field.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof StoryImpl)) {
+      return false;
+    }
+    StoryImpl o = (StoryImpl) obj;
+    return id == o.id;
+  }
+
+  public int getFragment() {
+    return fragment;
+  }
+
+  public int getId() {
+    return id;
+  }
+
+  /**
+   * Used internally, and not specified by the Story interface.
+   */
+  public int getLength() {
+    return length;
+  }
+
+  public String getLiteralTypeName() {
+    return literalDescription;
+  }
+
+  public SortedSet<Member> getMembers() {
+    return members;
+  }
+
+  public List<String> getMutations() {
+    return mutations;
+  }
+
+  public SortedSet<Origin> getSourceOrigin() {
+    return origins;
+  }
+
+  /**
+   * Identity is based on the <code>id</code> field.
+   */
+  @Override
+  public int hashCode() {
+    return id;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImplComparator.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImplComparator.java
new file mode 100644
index 0000000..f3daae8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImplComparator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.soyc.impl;
+
+import com.google.gwt.core.ext.soyc.Story;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Exists as a real class to allow TreeMaps to be serialized.
+ */
+public class StoryImplComparator implements Comparator<Story>, Serializable {
+  public int compare(Story o1, Story o2) {
+    return ((StoryImpl) o1).getId() - ((StoryImpl) o2).getId();
+  }
+
+  /**
+   * Use the singleton instance.
+   */
+  private Object readResolve() throws ObjectStreamException {
+    return StoryImpl.ID_COMPARATOR;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/package-info.java b/dev/core/src/com/google/gwt/core/ext/soyc/package-info.java
new file mode 100644
index 0000000..5cb0045
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * This package contains interfaces that provide access to
+ * "Story of Your Compile" information. When the compiler is run with analysis
+ * turned on, these types are available to Linkers via
+ * {@link com.google.gwt.core.ext.linker.CompilationAnalysis} artifacts.
+ */
+package com.google.gwt.core.ext.soyc;
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/linker/soyc/SoycReportLinker.java b/dev/core/src/com/google/gwt/core/linker/soyc/SoycReportLinker.java
new file mode 100644
index 0000000..4f74865
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/linker/soyc/SoycReportLinker.java
@@ -0,0 +1,863 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.linker.soyc;
+
+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.CompilationAnalysis;
+import com.google.gwt.core.ext.linker.CompilationResult;
+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.CompilationAnalysis.Snippet;
+import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.ext.soyc.ClassMember;
+import com.google.gwt.core.ext.soyc.FieldMember;
+import com.google.gwt.core.ext.soyc.FunctionMember;
+import com.google.gwt.core.ext.soyc.Member;
+import com.google.gwt.core.ext.soyc.MethodMember;
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.core.ext.soyc.Story;
+import com.google.gwt.core.ext.soyc.Story.Origin;
+import com.google.gwt.dev.util.HtmlTextOutput;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Generates the XML report containing the Story of Your Compile.
+ */
+@LinkerOrder(Order.PRE)
+public class SoycReportLinker extends AbstractLinker {
+
+  int curNumIndents = 0;
+
+  public String escapeXml(String unescaped) {
+    String escaped = unescaped.replaceAll("\\&", "&amp;");
+    escaped = escaped.replaceAll("\\<", "&lt;");
+    escaped = escaped.replaceAll("\\>", "&gt;");
+    escaped = escaped.replaceAll("\\\"", "&quot;");
+    // escaped = escaped.replaceAll("\\'", "&apos;");
+    return escaped;
+  }
+
+  @Override
+  public String getDescription() {
+    return "Story of your compile report";
+  }
+
+  @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
+    SortedSet<CompilationAnalysis> reports = artifacts.find(CompilationAnalysis.class);
+
+    // Do nothing if there are no reports to be generated.
+    if (reports.isEmpty()) {
+      return artifacts;
+    }
+
+    logger = logger.branch(TreeLogger.DEBUG, "SOYC report linker");
+    initialize(logger);
+
+    if (reports.isEmpty()) {
+      logger.log(TreeLogger.DEBUG, "No SOYC report artifacts");
+      return artifacts;
+    }
+
+    artifacts = new ArtifactSet(artifacts);
+    int reportNum = 0;
+    SortedMap<CompilationResult, String> partialPathsByResult = new TreeMap<CompilationResult, String>();
+
+    // TODO: This goes much faster in parallel, but what's the policy?
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    List<Future<SyntheticArtifact>> futures = new ArrayList<Future<SyntheticArtifact>>(
+        reports.size());
+    for (final CompilationAnalysis report : reports) {
+      final TreeLogger loopLogger = logger.branch(TreeLogger.SPAM,
+          "Report for " + report.toString());
+      final String reportName = "report" + reportNum++ + ".xml.gz";
+      partialPathsByResult.put(report.getCompilationResult(), reportName);
+      Future<SyntheticArtifact> future = executor.submit(new Callable<SyntheticArtifact>() {
+        public SyntheticArtifact call() throws Exception {
+          loopLogger.log(TreeLogger.INFO, "Started");
+          SyntheticArtifact reportArtifact = emitReport(loopLogger, report,
+              reportName, true);
+          return reportArtifact;
+        }
+      });
+      futures.add(future);
+    }
+    executor.shutdown();
+
+    for (Future<SyntheticArtifact> future : futures) {
+      SyntheticArtifact artifact;
+      try {
+        artifact = future.get();
+      } catch (InterruptedException e) {
+        logger.log(TreeLogger.ERROR, "Unable to process report", e);
+        throw new UnableToCompleteException();
+      } catch (ExecutionException e) {
+        logger.log(TreeLogger.ERROR, "Unable to process report", e);
+        throw new UnableToCompleteException();
+      }
+      artifact.setPrivate(true);
+      artifacts.add(artifact);
+    }
+
+    // Emit manifest
+    try {
+      SyntheticArtifact sa = emitManifest(logger, artifacts,
+          partialPathsByResult, false);
+      sa.setPrivate(true);
+      artifacts.add(sa);
+    } catch (FileNotFoundException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+
+    return artifacts;
+  }
+
+  private void emitAliases(HtmlTextOutput htmlOut, Set<String> methodAliases) {
+    String curLine;
+    if (methodAliases.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+
+      curLine = "<aliases>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+    }
+
+    for (String methodAlias : methodAliases) {
+      curLine = "<alias jsName=\"" + methodAlias + "\"/>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+    if (methodAliases.size() > 0) {
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+
+      curLine = "</aliases>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+  }
+
+  private void emitClasses(HtmlTextOutput htmlOut,
+      SortedMap<String, Set<ClassMember>> packageToClasses, String packageName) {
+    String curLine;
+    /**
+     * sort the classes alphabetically
+     */
+    TreeMap<String, ClassMember> sortedClasses = new TreeMap<String, ClassMember>();
+    for (ClassMember classMember : packageToClasses.get(packageName)) {
+      String className = classMember.getSourceName();
+      sortedClasses.put(className, classMember);
+    }
+
+    for (String className : sortedClasses.keySet()) {
+      ClassMember classMember = sortedClasses.get(className);
+      curLine = "<class id=\"" + className + "\" ";
+      htmlOut.printRawOpt(curLine);
+
+      String jsName = classMember.getJsName();
+      String name = className.substring(className.lastIndexOf('.') + 1);
+      curLine = "jsName=\"" + jsName + "\" name=\"" + name + "\">";
+
+      if (jsName == null) {
+        curLine = "name=\"" + name + "\">";
+      }
+
+      emitOverrides(htmlOut, curLine, classMember);
+      emitDependencies(htmlOut, classMember);
+      emitMethods(htmlOut, classMember);
+      emitFields(htmlOut, classMember);
+
+      curLine = "</class>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+  }
+
+  private void emitDependencies(HtmlTextOutput htmlOut, ClassMember classMember) {
+    String curLine;
+    Set<Member> dependencies = classMember.getDependencies();
+    if (dependencies.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+
+      curLine = "<depends>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+    }
+    for (Member dependency : dependencies) {
+      curLine = "<on idref=\"" + dependency.getSourceName() + "\"/>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+    if (dependencies.size() > 0) {
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+
+      curLine = "</depends>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+  }
+
+  private void emitFields(HtmlTextOutput htmlOut, ClassMember classMember) {
+    String curLine;
+    Set<FieldMember> fields = classMember.getFields();
+    if (fields.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+    }
+    for (FieldMember field : fields) {
+      curLine = "<field id=\"" + field.getSourceName() + "\" jsName=\""
+          + field.getJsName() + "\"/>";
+      String curJsName = field.getJsName();
+      if (curJsName == null) {
+        curLine = "<field id=\"" + field.getSourceName() + "\"/>";
+      }
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+    if (fields.size() > 0) {
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+  }
+
+  private void emitFunctions(CompilationAnalysis report, HtmlTextOutput htmlOut) {
+    String curLine;
+
+    Set<FunctionMember> functions = report.getFunctions();
+    for (FunctionMember function : functions) {
+      curLine = "<function ";
+      htmlOut.printRawOpt(curLine);
+
+      String sourceName = function.getSourceName();
+      String jsName = function.getJsName();
+      Set<Member> dependencies = function.getDependencies();
+      if (dependencies.size() == 0) {
+        curLine = "id=\"" + sourceName + "\" jsName=\"" + jsName + "\"/>";
+        if (jsName == null) {
+          curLine = "id=\"" + sourceName + "\"/>";
+        }
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newline();
+      } else {
+        curLine = "id=\"" + sourceName + "\" jsName=\"" + jsName + "\">";
+        if (jsName == null) {
+          curLine = "id=\"" + sourceName + "\">";
+        }
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newline();
+        htmlOut.indentIn();
+        htmlOut.indentIn();
+        curNumIndents++;
+
+        curLine = "<depends>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newline();
+        htmlOut.indentIn();
+        htmlOut.indentIn();
+        curNumIndents++;
+      }
+      for (Member dependency : dependencies) {
+        curLine = "<on idref=\"" + dependency.getSourceName() + "\"/>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newline();
+      }
+      if (dependencies.size() > 0) {
+        htmlOut.indentOut();
+        htmlOut.indentOut();
+        curNumIndents--;
+        curLine = "</depends>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newline();
+        htmlOut.indentOut();
+        htmlOut.indentOut();
+        curNumIndents--;
+
+        curLine = "</function>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      }
+    }
+  }
+
+  private void emitJs(CompilationAnalysis report, HtmlTextOutput htmlOut,
+      Map<Story, Integer> storyIds) {
+
+    String curLine;
+    int fragment = 0;
+    for (String contents : report.getCompilationResult().getJavaScript()) {
+      curLine = "<js fragment=\"" + fragment + "\">";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+
+      for (Snippet snippet : report.getSnippets(fragment)) {
+        Range range = snippet.getRange();
+        Story story = snippet.getStory();
+        assert storyIds.containsKey(story);
+        int storyId = storyIds.get(story);
+
+        String jsCode = contents.substring(range.getStart(), range.getEnd());
+        jsCode = escapeXml(jsCode);
+        if ((jsCode.length() == 0) || (jsCode.compareTo("\n") == 0)) {
+          curLine = "<storyref idref=\"story" + Integer.toString(storyId)
+              + "\"/>";
+        } else {
+          curLine = "<storyref idref=\"story" + Integer.toString(storyId)
+              + "\">" + jsCode + "</storyref>";
+        }
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      }
+
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+
+      curLine = "</js>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      fragment++;
+    }
+  }
+
+  private SyntheticArtifact emitManifest(TreeLogger logger,
+      ArtifactSet artifacts,
+      SortedMap<CompilationResult, String> partialPathsByResult,
+      boolean compress) throws UnableToCompleteException, IOException {
+
+    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+    OutputStreamWriter out;
+    try {
+      out = new OutputStreamWriter(compress ? new GZIPOutputStream(bytes)
+          : bytes);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to set up gzip stream", e);
+      throw new UnableToCompleteException();
+    }
+    PrintWriter pw = new PrintWriter(out);
+    HtmlTextOutput htmlOut = new HtmlTextOutput(pw, false);
+
+    String curLine = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newline();
+    curLine = "<soyc-manifest>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newline();
+    htmlOut.indentIn();
+    htmlOut.indentIn();
+    curNumIndents++;
+
+    for (Map.Entry<CompilationResult, String> entry : partialPathsByResult.entrySet()) {
+      curLine = "<report href=\"" + entry.getValue() + "\">";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newline();
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+
+      for (Map<SelectionProperty, String> map : entry.getKey().getPropertyMap()) {
+
+        if (map.size() > 0) {
+          curLine = "<permutation>";
+          htmlOut.printRawOpt(curLine);
+          htmlOut.newline();
+          htmlOut.indentIn();
+          htmlOut.indentIn();
+          curNumIndents++;
+
+        } else {
+          curLine = "<permutation/>";
+          htmlOut.printRawOpt(curLine);
+          htmlOut.newline();
+        }
+        for (Map.Entry<SelectionProperty, String> propertyEntry : map.entrySet()) {
+          curLine = "<property name=\"" + propertyEntry.getKey().getName()
+              + "\" value=\"" + propertyEntry.getValue() + "\"/>";
+          htmlOut.printRawOpt(curLine);
+          htmlOut.newline();
+        }
+        if (map.size() > 0) {
+          htmlOut.indentOut();
+          htmlOut.indentOut();
+          curNumIndents--;
+
+          curLine = "</permutation>";
+          htmlOut.printRawOpt(curLine);
+          htmlOut.newline();
+        }
+      }
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+
+      curLine = "</report>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newline();
+    }
+
+    htmlOut.indentOut();
+    htmlOut.indentOut();
+    curNumIndents--;
+
+    curLine = "</soyc-manifest>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newline();
+
+    pw.close();
+    Utility.close(out);
+    SyntheticArtifact toReturn = emitBytes(logger, bytes.toByteArray(),
+        "manifest.xml");
+
+    return toReturn;
+  }
+
+  private void emitMembers(CompilationAnalysis report, HtmlTextOutput htmlOut) {
+    String curLine;
+
+    curLine = "<members>";
+
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+    htmlOut.indentIn();
+    htmlOut.indentIn();
+    curNumIndents++;
+
+    SortedMap<String, Set<ClassMember>> packageToClasses = new TreeMap<String, Set<ClassMember>>();
+
+    emitPackages(report, htmlOut, packageToClasses);
+    emitFunctions(report, htmlOut);
+
+    htmlOut.indentOut();
+    htmlOut.indentOut();
+    curNumIndents--;
+
+    curLine = "</members>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+  }
+
+  private void emitMethodDependencies(HtmlTextOutput htmlOut,
+      Set<Member> methodDependencies) {
+    String curLine;
+    if (methodDependencies.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+
+      curLine = "<depends>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+
+      for (Member methodDependency : methodDependencies) {
+        curLine = "<on idref=\"" + methodDependency.getSourceName() + "\"/>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      }
+
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+
+      curLine = "</depends>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+  }
+
+  private void emitMethodOverrides(HtmlTextOutput htmlOut,
+      Set<MethodMember> methodOverrides) {
+    String curLine;
+    if (methodOverrides.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+
+      curLine = "<override>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+    }
+    for (MethodMember overrideMethodMember : methodOverrides) {
+      curLine = "<of idref=\"" + overrideMethodMember.getSourceName() + "\"/>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+    if (methodOverrides.size() > 0) {
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+
+      curLine = "</override>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+  }
+
+  private void emitMethods(HtmlTextOutput htmlOut, ClassMember classMember) {
+    String curLine;
+    Set<MethodMember> methods = classMember.getMethods();
+    if (methods.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+    }
+    for (MethodMember method : methods) {
+      curLine = "<method ";
+      htmlOut.printRawOpt(curLine);
+
+      String jsAtt = " jsName=\"" + method.getJsName() + "\"";
+      String curJsName = method.getJsName();
+
+      if (curJsName == null) {
+        jsAtt = "";
+      }
+
+      Set<String> methodAliases = method.getJsAliases();
+      Set<MethodMember> methodOverrides = method.getOverrides();
+      Set<Member> methodDependencies = method.getDependencies();
+
+      if ((methodOverrides.size() > 0) || (methodDependencies.size() > 0)
+          || (methodAliases.size() > 0)) {
+        curLine = "id=\"" + method.getSourceName() + "\"" + jsAtt + ">";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      } else {
+        curLine = "id=\"" + method.getSourceName() + "\"" + jsAtt + "/>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      }
+
+      emitAliases(htmlOut, methodAliases);
+      emitMethodOverrides(htmlOut, methodOverrides);
+      emitMethodDependencies(htmlOut, methodDependencies);
+
+      if ((methodOverrides.size() > 0) || (methodDependencies.size() > 0)
+          || (methodAliases.size() > 0)) {
+        curLine = "</method>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      }
+    }
+
+    if (methods.size() > 0) {
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+  }
+
+  private void emitOverrides(HtmlTextOutput htmlOut, String curLine,
+      ClassMember classMember) {
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+    Set<ClassMember> overrides = classMember.getOverrides();
+    if (overrides.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+      curLine = "<override>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+    }
+    for (ClassMember overrideClassMember : overrides) {
+      curLine = "<of idref=\"" + overrideClassMember.getSourceName() + "\"/>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+    if (overrides.size() > 0) {
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+
+      curLine = "</override>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+  }
+
+  private void emitPackages(CompilationAnalysis report, HtmlTextOutput htmlOut,
+      SortedMap<String, Set<ClassMember>> packageToClasses) {
+
+    String curLine;
+    for (ClassMember classMember : report.getClasses()) {
+      String packageName = classMember.getPackage();
+      if (packageToClasses.containsKey(packageName)) {
+        packageToClasses.get(packageName).add(classMember);
+      } else {
+        Set<ClassMember> insertSet = new HashSet<ClassMember>();
+        insertSet.add(classMember);
+        packageToClasses.put(packageName, insertSet);
+      }
+    }
+
+    for (String packageName : packageToClasses.keySet()) {
+
+      curLine = "<package id=\"" + packageName + "\">";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+
+      if (packageToClasses.get(packageName).size() > 0) {
+        htmlOut.indentIn();
+        htmlOut.indentIn();
+        curNumIndents++;
+      }
+      emitClasses(htmlOut, packageToClasses, packageName);
+      if (packageToClasses.get(packageName).size() > 0) {
+        htmlOut.indentOut();
+        htmlOut.indentOut();
+        curNumIndents--;
+      }
+
+      curLine = "</package>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+  }
+
+  private SyntheticArtifact emitReport(TreeLogger logger,
+      CompilationAnalysis report, String partialPath, boolean compress)
+      throws UnableToCompleteException, IOException {
+
+    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+    OutputStreamWriter out;
+    try {
+      out = new OutputStreamWriter(compress ? new GZIPOutputStream(bytes)
+          : bytes);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to set up gzip stream", e);
+      throw new UnableToCompleteException();
+    }
+    PrintWriter pw = new PrintWriter(out);
+    HtmlTextOutput htmlOut = new HtmlTextOutput(pw, false);
+
+    String curLine = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+    curLine = "<soyc>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+    htmlOut.indentIn();
+    htmlOut.indentIn();
+    curNumIndents++;
+
+    emitMembers(report, htmlOut);
+    Map<Story, Integer> storyIds = emitStories(report, htmlOut);
+    emitJs(report, htmlOut, storyIds);
+
+    htmlOut.indentOut();
+    htmlOut.indentOut();
+    curNumIndents--;
+
+    curLine = "</soyc>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+
+    pw.close();
+    Utility.close(out);
+    SyntheticArtifact toReturn = emitBytes(logger, bytes.toByteArray(),
+        partialPath);
+
+    return toReturn;
+  }
+
+  private Map<Story, Integer> emitStories(CompilationAnalysis report,
+      HtmlTextOutput htmlOut) {
+
+    String curLine;
+    Map<Story, Integer> storyIds = new HashMap<Story, Integer>();
+    Set<Story> stories = report.getStories();
+    curLine = "<stories>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+
+    if (stories.size() > 0) {
+      htmlOut.indentIn();
+      htmlOut.indentIn();
+      curNumIndents++;
+    }
+    for (Story story : stories) {
+
+      int storyNum = storyIds.size();
+      storyIds.put(story, storyNum);
+
+      curLine = "<story id=\"story" + Integer.toString(storyNum) + "\"";
+      if (story.getLiteralTypeName() != null) {
+        curLine = curLine + " literal=\"" + story.getLiteralTypeName() + "\"";
+      }
+      curLine = curLine + ">";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+
+      Set<Origin> origins = story.getSourceOrigin();
+      if (origins.size() > 0) {
+        htmlOut.indentIn();
+        htmlOut.indentIn();
+        curNumIndents++;
+
+        curLine = "<origins>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+        htmlOut.indentIn();
+        htmlOut.indentIn();
+        curNumIndents++;
+      }
+      for (Origin origin : origins) {
+        curLine = "<origin lineNumber=\""
+            + Integer.toString(origin.getLineNumber()) + "\" location=\""
+            + origin.getLocation() + "\"/>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      }
+      if (origins.size() > 0) {
+        htmlOut.indentOut();
+        htmlOut.indentOut();
+        curNumIndents--;
+
+        curLine = "</origins>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+
+        htmlOut.indentOut();
+        htmlOut.indentOut();
+        curNumIndents--;
+      }
+
+      Set<Member> correlations = story.getMembers();
+      if (correlations.size() > 0) {
+        htmlOut.indentIn();
+        htmlOut.indentIn();
+        curNumIndents++;
+
+        curLine = "<correlations>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+
+        htmlOut.indentIn();
+        htmlOut.indentIn();
+        curNumIndents++;
+      }
+      for (Member correlation : correlations) {
+        curLine = "<by idref=\"" + correlation.getSourceName() + "\"/>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+      }
+      if (correlations.size() > 0) {
+        htmlOut.indentOut();
+        htmlOut.indentOut();
+        curNumIndents--;
+
+        curLine = "</correlations>";
+        htmlOut.printRawOpt(curLine);
+        htmlOut.newlineOpt();
+
+        htmlOut.indentOut();
+        htmlOut.indentOut();
+        curNumIndents--;
+      }
+
+      curLine = "</story>";
+      htmlOut.printRawOpt(curLine);
+      htmlOut.newlineOpt();
+    }
+    if (stories.size() > 0) {
+      htmlOut.indentOut();
+      htmlOut.indentOut();
+      curNumIndents--;
+    }
+    curLine = "</stories>";
+    htmlOut.printRawOpt(curLine);
+    htmlOut.newlineOpt();
+
+    return storyIds;
+  }
+
+  private void initialize(TreeLogger logger) throws UnableToCompleteException {
+    logger = logger.branch(TreeLogger.SPAM, "Initializing");
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index ec0e924..106c2e1 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -20,11 +20,14 @@
 import com.google.gwt.dev.CompileTaskRunner.CompileTask;
 import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
 import com.google.gwt.util.tools.ArgHandlerString;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -176,13 +179,8 @@
   public static PermutationResult compile(TreeLogger logger,
       Permutation permutation, UnifiedAst unifiedAst) {
     try {
-      final String js[] = JavaToJavaScriptCompiler.compilePermutation(logger,
-          unifiedAst, permutation.getRebindAnswers());
-      return new PermutationResult() {
-        public String[] getJs() {
-          return js;
-        }
-      };
+      return JavaToJavaScriptCompiler.compilePermutation(logger, unifiedAst,
+          permutation.getRebindAnswers());
     } catch (UnableToCompleteException e) {
       // We intentionally don't pass in the exception here since the real
       // cause has been logged.
@@ -195,7 +193,8 @@
    */
   public static boolean compile(TreeLogger logger,
       Precompilation precompilation, Permutation[] perms, int localWorkers,
-      File[] resultFiles) throws UnableToCompleteException {
+      List<FileBackedObject<PermutationResult>> resultFiles)
+      throws UnableToCompleteException {
     final TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling "
         + perms.length + " permutations");
     PermutationWorkerFactory.compilePermutations(logger, precompilation, perms,
@@ -227,12 +226,16 @@
     System.exit(1);
   }
 
-  public static File[] makeResultFiles(File compilerWorkDir, Permutation[] perms) {
-    File[] resultFiles = new File[perms.length];
+  public static List<FileBackedObject<PermutationResult>> makeResultFiles(
+      File compilerWorkDir, Permutation[] perms) {
+    List<FileBackedObject<PermutationResult>> toReturn = new ArrayList<FileBackedObject<PermutationResult>>(
+        perms.length);
     for (int i = 0; i < perms.length; ++i) {
-      resultFiles[i] = makePermFilename(compilerWorkDir, perms[i].getId());
+      File f = makePermFilename(compilerWorkDir, perms[i].getId());
+      toReturn.add(new FileBackedObject<PermutationResult>(
+          PermutationResult.class, f));
     }
-    return resultFiles;
+    return toReturn;
   }
 
   /**
@@ -291,7 +294,8 @@
       }
     }
 
-    File[] resultFiles = makeResultFiles(options.getCompilerWorkDir(), perms);
+    List<FileBackedObject<PermutationResult>> resultFiles = makeResultFiles(
+        options.getCompilerWorkDir(), perms);
     return compile(logger, precompilation, subPerms, options.getLocalWorkers(),
         resultFiles);
   }
diff --git a/dev/core/src/com/google/gwt/dev/CompilePermsServer.java b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
index ace3385..df14e98 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
@@ -16,8 +16,10 @@
 package com.google.gwt.dev;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
 import com.google.gwt.dev.util.arg.OptionLogLevel;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
@@ -289,20 +291,27 @@
     } catch (ClassNotFoundException e) {
       logger.log(TreeLogger.ERROR, "Probable client/server mismatch or "
           + "classpath misconfiguration", e);
+    } catch (UnableToCompleteException e) {
+      logger.log(TreeLogger.ERROR, "Internal compiler error", e);
     }
     return false;
   }
 
   static void compilePermutation(TreeLogger logger, UnifiedAst ast,
       ObjectInputStream in, ObjectOutputStream out)
-      throws ClassNotFoundException, IOException {
+      throws ClassNotFoundException, IOException, UnableToCompleteException {
+    FileBackedObject<PermutationResult> resultFile = (FileBackedObject<PermutationResult>) in.readObject();
     Permutation p = (Permutation) in.readObject();
     logger.log(TreeLogger.SPAM, "Permutation read");
 
     PermutationResult result = CompilePerms.compile(logger.branch(
         TreeLogger.DEBUG, "Compiling"), p, ast);
     logger.log(TreeLogger.DEBUG, "Successfully compiled permutation");
-    out.writeObject(result);
+
+    resultFile.set(logger, result);
+
+    // Send a placeholder null indicating no Throwable
+    out.writeObject(null);
     out.flush();
     logger.log(TreeLogger.SPAM, "Sent result");
   }
diff --git a/dev/core/src/com/google/gwt/dev/ExternalPermutationWorkerFactory.java b/dev/core/src/com/google/gwt/dev/ExternalPermutationWorkerFactory.java
index 137144d..312e4bd 100644
--- a/dev/core/src/com/google/gwt/dev/ExternalPermutationWorkerFactory.java
+++ b/dev/core/src/com/google/gwt/dev/ExternalPermutationWorkerFactory.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.Util;
 
 import java.io.BufferedReader;
@@ -99,7 +100,8 @@
       this.serverSocket = sock;
     }
 
-    public PermutationResult compile(TreeLogger logger, Permutation permutation)
+    public void compile(TreeLogger logger, Permutation permutation,
+        FileBackedObject<PermutationResult> resultFile)
         throws TransientWorkerException, UnableToCompleteException {
 
       // If we've just started, we need to get a connection from a subprocess
@@ -139,9 +141,16 @@
 
       try {
         out.writeBoolean(true);
+        out.writeObject(resultFile);
         out.writeObject(permutation);
         out.flush();
-        return (PermutationResult) in.readObject();
+
+        Throwable t = (Throwable) in.readObject();
+        if (t != null) {
+          logger.log(TreeLogger.ERROR, "Error from external worker", t);
+          throw new UnableToCompleteException();
+        }
+
       } catch (IOException e) {
         logger.log(TreeLogger.WARN, "Lost communication with remote process", e);
         throw new TransientWorkerException(
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index 546e5ab..4a74a45 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -22,16 +22,19 @@
 import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
 import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.dev.util.arg.ArgHandlerSoyc;
 import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
 import com.google.gwt.util.tools.Utility;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
 
 /**
  * The main executable entry point for the GWT Java to JavaScript compiler.
@@ -48,6 +51,7 @@
       registerHandler(new ArgHandlerExtraDir(options));
       registerHandler(new ArgHandlerLocalWorkers(options));
       registerHandler(new ArgHandlerOutDir(options));
+      registerHandler(new ArgHandlerSoyc(options));
     }
 
     @Override
@@ -153,7 +157,7 @@
             module, options.getGenDir(), options.getCompilerWorkDir());
 
         Permutation[] allPerms = precompilation.getPermutations();
-        File[] resultFiles = CompilePerms.makeResultFiles(
+        List<FileBackedObject<PermutationResult>> resultFiles = CompilePerms.makeResultFiles(
             options.getCompilerWorkDir(), allPerms);
         CompilePerms.compile(logger, precompilation, allPerms,
             options.getLocalWorkers(), resultFiles);
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index f10d910..99d2252 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -26,6 +26,7 @@
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
+import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
@@ -33,7 +34,9 @@
 import com.google.gwt.dev.util.arg.OptionOutDir;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -100,7 +103,8 @@
   }
 
   public static ArtifactSet link(TreeLogger logger, ModuleDef module,
-      Precompilation precompilation, File[] resultFiles)
+      Precompilation precompilation,
+      List<FileBackedObject<PermutationResult>> resultFiles)
       throws UnableToCompleteException {
     StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
         module, precompilation.getUnifiedAst().getOptions());
@@ -108,7 +112,8 @@
   }
 
   public static void link(TreeLogger logger, ModuleDef module,
-      Precompilation precompilation, File[] resultFiles, File outDir,
+      Precompilation precompilation,
+      List<FileBackedObject<PermutationResult>> resultFiles, File outDir,
       File extrasDir) throws UnableToCompleteException {
     StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
         module, precompilation.getUnifiedAst().getOptions());
@@ -142,15 +147,16 @@
 
   private static ArtifactSet doLink(TreeLogger logger,
       StandardLinkerContext linkerContext, Precompilation precompilation,
-      File[] resultFiles) throws UnableToCompleteException {
+      List<FileBackedObject<PermutationResult>> resultFiles)
+      throws UnableToCompleteException {
     Permutation[] perms = precompilation.getPermutations();
-    if (perms.length != resultFiles.length) {
+    if (perms.length != resultFiles.size()) {
       throw new IllegalArgumentException(
           "Mismatched resultFiles.length and permutation count");
     }
 
     for (int i = 0; i < perms.length; ++i) {
-      finishPermuation(logger, perms[i], resultFiles[i], linkerContext);
+      finishPermuation(logger, perms[i], resultFiles.get(i), linkerContext);
     }
 
     linkerContext.addOrReplaceArtifacts(precompilation.getGeneratedArtifacts());
@@ -200,10 +206,10 @@
   }
 
   private static void finishPermuation(TreeLogger logger, Permutation perm,
-      File jsFile, StandardLinkerContext linkerContext)
-      throws UnableToCompleteException {
+      FileBackedObject<PermutationResult> resultFile,
+      StandardLinkerContext linkerContext) throws UnableToCompleteException {
     StandardCompilationResult compilation = linkerContext.getCompilation(
-        logger, jsFile);
+        logger, resultFile);
     StaticPropertyOracle[] propOracles = perm.getPropertyOracles();
     for (StaticPropertyOracle propOracle : propOracles) {
       BindingProperty[] orderedProps = propOracle.getOrderedProps();
@@ -254,16 +260,20 @@
       return false;
     }
     Permutation[] perms = precompilation.getPermutations();
-    File[] resultFiles = new File[perms.length];
+
+    List<FileBackedObject<PermutationResult>> resultFiles = new ArrayList<FileBackedObject<PermutationResult>>(
+        perms.length);
     for (int i = 0; i < perms.length; ++i) {
-      resultFiles[i] = CompilePerms.makePermFilename(
+      File permFile = CompilePerms.makePermFilename(
           options.getCompilerWorkDir(), i);
-      if (!resultFiles[i].exists()) {
+      if (!permFile.exists()) {
         logger.log(TreeLogger.ERROR, "File not found '"
             + precompilationFile.getAbsolutePath()
             + "'; please compile all permutations");
         return false;
       }
+      resultFiles.add(new FileBackedObject<PermutationResult>(
+          PermutationResult.class, permFile));
     }
 
     TreeLogger branch = logger.branch(TreeLogger.INFO, "Linking module "
diff --git a/dev/core/src/com/google/gwt/dev/PermutationCompiler.java b/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
deleted file mode 100644
index 5ae54b4..0000000
--- a/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.cfg.BindingProperty;
-import com.google.gwt.dev.cfg.StaticPropertyOracle;
-import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
-import com.google.gwt.dev.jjs.UnifiedAst;
-import com.google.gwt.dev.util.PerfLogger;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Compiles a set of permutations, possibly in parallel in multiple threads.
- */
-public class PermutationCompiler {
-
-  /**
-   * Hands back results as they are finished.
-   */
-  public interface ResultsHandler {
-    void addResult(Permutation permutation, int permNum, String[] js)
-        throws UnableToCompleteException;
-  }
-
-  /**
-   * A Result for a permutation that failed to compile.
-   */
-  private static final class FailedResult extends Result {
-    private Throwable exception;
-
-    public FailedResult(Permutation perm, int permNum, Throwable exception) {
-      super(perm, permNum);
-      this.exception = exception;
-    }
-
-    public Throwable getException() {
-      return exception;
-    }
-  }
-
-  /**
-   * Represents the task of compiling a single permutation.
-   */
-  private static final class PermutationTask implements Callable<String[]> {
-    private static void logProperties(TreeLogger logger,
-        StaticPropertyOracle[] propOracles) {
-      for (StaticPropertyOracle propOracle : propOracles) {
-        BindingProperty[] props = propOracle.getOrderedProps();
-        String[] values = propOracle.getOrderedPropValues();
-        if (logger.isLoggable(TreeLogger.DEBUG)) {
-          logger = logger.branch(TreeLogger.DEBUG, "Setting properties", null);
-          for (int i = 0; i < props.length; i++) {
-            String name = props[i].getName();
-            String value = values[i];
-            logger.log(TreeLogger.TRACE, name + " = " + value, null);
-          }
-        }
-      }
-    }
-
-    private final UnifiedAst unifiedAst;
-    private final TreeLogger logger;
-    private final Permutation perm;
-    private final int permNum;
-
-    public PermutationTask(TreeLogger logger, UnifiedAst unifiedAst,
-        Permutation perm, int permNum) {
-      this.logger = logger;
-      this.unifiedAst = unifiedAst;
-      this.perm = perm;
-      this.permNum = permNum;
-    }
-
-    public String[] call() throws Exception {
-      PerfLogger.start("Permutation #" + permNum);
-      try {
-        TreeLogger branch = logger.branch(TreeLogger.TRACE, "Permutation #"
-            + permNum);
-        logProperties(branch, perm.getPropertyOracles());
-        return JavaToJavaScriptCompiler.compilePermutation(branch, unifiedAst,
-            perm.getRebindAnswers());
-      } finally {
-        PerfLogger.end();
-      }
-    }
-
-    public int getPermNum() {
-      return permNum;
-    }
-
-    public Permutation getPermutation() {
-      return perm;
-    }
-  }
-
-  /**
-   * Contains the results of an attempt to compile.
-   */
-  private abstract static class Result {
-    private final Permutation perm;
-    private final int permNum;
-
-    public Result(Permutation perm, int permNum) {
-      this.perm = perm;
-      this.permNum = permNum;
-    }
-
-    public int getPermNum() {
-      return permNum;
-    }
-
-    public Permutation getPermutation() {
-      return perm;
-    }
-  }
-
-  /**
-   * A Result for a permutation that succeeded.
-   */
-  private static final class SuccessResult extends Result {
-    private final String[] js;
-
-    public SuccessResult(Permutation perm, int permNum, String[] result) {
-      super(perm, permNum);
-      this.js = result;
-    }
-
-    public String[] getJs() {
-      return js;
-    }
-  }
-
-  /**
-   * Implements a memory-sensitive worker thread to compile permutations.
-   */
-  private class WorkerThread implements Runnable {
-    private PermutationTask currentTask;
-
-    private final Runnable outOfMemoryRetryAction = new Runnable() {
-      public void run() {
-        currentTask.logger.log(
-            TreeLogger.WARN,
-            "Not enough memory to run another concurrent permutation, reducing thread count; "
-                + "increasing the amount of memory by using the -Xmx flag "
-                + "at startup may result in faster compiles");
-        tasks.add(currentTask);
-      }
-    };
-
-    public void run() {
-      try {
-        while (true) {
-          doTask();
-        }
-      } catch (ThreadDeath expected) {
-      }
-    }
-
-    protected void doTask() throws ThreadDeath {
-      currentTask = tasks.poll();
-      if (currentTask == null) {
-        // Nothing left to do.
-        tryToExitNonFinalThread(null);
-
-        // As the last thread, I must inform the main thread we're all done.
-        exitFinalThread(new Runnable() {
-          public void run() {
-            results.add(FINISHED_RESULT);
-          }
-        });
-      }
-
-      boolean definitelyFinalThread = (threadCount.get() == 1);
-      try {
-        String[] result = currentTask.call();
-        results.add(new SuccessResult(currentTask.getPermutation(),
-            currentTask.getPermNum(), result));
-      } catch (OutOfMemoryError e) {
-        if (definitelyFinalThread) {
-          // OOM on the final thread, this is a truly unrecoverable failure.
-          currentTask.logger.log(TreeLogger.ERROR, "Out of memory", e);
-          exitFinalThread(new Runnable() {
-            public void run() {
-              results.add(new FailedResult(currentTask.getPermutation(),
-                  currentTask.getPermNum(), new UnableToCompleteException()));
-            }
-          });
-        }
-
-        /*
-         * Try the task again with fewer threads, it may not OOM this time.
-         */
-        tryToExitNonFinalThread(outOfMemoryRetryAction);
-
-        /*
-         * Okay, so we actually are the final thread. However, we weren't the
-         * final thread at the beginning of the compilation, so it's possible
-         * that a retry may now succeed with only one active thread. Let's
-         * optimistically retry one last time, and if this doesn't work, it's a
-         * hard failure.
-         */
-        outOfMemoryRetryAction.run();
-      } catch (Throwable e) {
-        // Unexpected error compiling, this is unrecoverable.
-        results.add(new FailedResult(currentTask.getPermutation(),
-            currentTask.getPermNum(), e));
-        throw new ThreadDeath();
-      }
-    }
-
-    private void exitFinalThread(Runnable actionOnExit) {
-      boolean isFinalThread = threadCount.compareAndSet(1, 0);
-      assert isFinalThread;
-      if (actionOnExit != null) {
-        actionOnExit.run();
-      }
-      throw new ThreadDeath();
-    }
-
-    /**
-     * Exits this thread if and only if it's not the last running thread,
-     * performing the specified action before terminating.
-     * 
-     * @param actionOnExit
-     */
-    private void tryToExitNonFinalThread(Runnable actionOnExit) {
-      int remainingThreads = threadCount.decrementAndGet();
-      if (remainingThreads == 0) {
-        // We are definitely the last thread.
-        threadCount.incrementAndGet();
-        return;
-      }
-
-      // We are definitely not the last thread, and have removed our count.
-      if (actionOnExit != null) {
-        actionOnExit.run();
-      }
-      throw new ThreadDeath();
-    }
-  }
-
-  /**
-   * A marker Result that tells the main thread all work is done.
-   */
-  private static final Result FINISHED_RESULT = new Result(null, -1) {
-  };
-
-  /**
-   * A queue of results being sent from worker threads to the main thread.
-   */
-  protected final BlockingQueue<Result> results = new LinkedBlockingQueue<Result>();
-
-  /**
-   * A queue of tasks being sent to the worker threads.
-   */
-  protected final ConcurrentLinkedQueue<PermutationTask> tasks = new ConcurrentLinkedQueue<PermutationTask>();
-
-  /**
-   * Tracks the number of live worker threads.
-   */
-  protected final AtomicInteger threadCount = new AtomicInteger();
-
-  private final TreeLogger logger;
-
-  public PermutationCompiler(TreeLogger logger, UnifiedAst unifiedAst,
-      Permutation[] perms, int[] permsToRun) {
-    this.logger = logger;
-    for (int permToRun : permsToRun) {
-      tasks.add(new PermutationTask(logger, unifiedAst, perms[permToRun],
-          permToRun));
-    }
-  }
-
-  public void go(ResultsHandler handler) throws UnableToCompleteException {
-    int initialThreadCount = computeInitialThreadCount();
-    Thread[] workerThreads = new Thread[initialThreadCount];
-    for (int i = 0; i < initialThreadCount; ++i) {
-      workerThreads[i] = new Thread(new WorkerThread());
-    }
-    threadCount.set(initialThreadCount);
-    for (Thread thread : workerThreads) {
-      thread.start();
-    }
-    try {
-      while (true) {
-        Result result = results.take();
-        if (result == FINISHED_RESULT) {
-          assert threadCount.get() == 0;
-          return;
-        } else if (result instanceof SuccessResult) {
-          String[] js = ((SuccessResult) result).getJs();
-          handler.addResult(result.getPermutation(), result.getPermNum(), js);
-        } else if (result instanceof FailedResult) {
-          FailedResult failedResult = (FailedResult) result;
-          throw logAndTranslateException(failedResult.getException());
-        }
-        // Allow GC.
-        result = null;
-      }
-
-    } catch (InterruptedException e) {
-      throw new RuntimeException("Unexpected interruption", e);
-    } finally {
-      for (Thread thread : workerThreads) {
-        if (thread.isAlive()) {
-          thread.interrupt();
-        }
-      }
-    }
-  }
-
-  private int computeInitialThreadCount() {
-    /*
-     * Don't need more threads than the number of permutations.
-     */
-    int result = tasks.size();
-
-    /*
-     * Computation is mostly CPU bound, so don't use more threads than
-     * processors.
-     */
-    result = Math.min(Runtime.getRuntime().availableProcessors(), result);
-
-    /*
-     * User-defined value caps.
-     */
-    result = Math.min(result, Integer.getInteger("gwt.jjs.maxThreads", 1));
-
-    return result;
-  }
-
-  private UnableToCompleteException logAndTranslateException(Throwable e) {
-    if (e instanceof UnableToCompleteException) {
-      return (UnableToCompleteException) e;
-    } else {
-      logger.log(TreeLogger.ERROR, "Unexpected compiler failure", e);
-      return new UnableToCompleteException();
-    }
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/PermutationResult.java b/dev/core/src/com/google/gwt/dev/PermutationResult.java
index 0b6f1bf..41fe2b0 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationResult.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationResult.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.dev;
 
+import com.google.gwt.core.ext.linker.ArtifactSet;
+
 import java.io.Serializable;
 
 /**
@@ -22,6 +24,12 @@
  */
 public interface PermutationResult extends Serializable {
   /**
+   * Returns any Artifacts that may have been created as a result of compiling
+   * the permutation.
+   */
+  ArtifactSet getArtifacts();
+
+  /**
    * The compiled JavaScript code.
    */
   String[] getJs();
diff --git a/dev/core/src/com/google/gwt/dev/PermutationWorker.java b/dev/core/src/com/google/gwt/dev/PermutationWorker.java
index f24d35f..be16ba2 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationWorker.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationWorker.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.util.FileBackedObject;
 
 /**
  * Represents a facility that can compile an individual {@link Permutation}.
@@ -28,13 +29,15 @@
   /**
    * Compile a single permutation. The {@link com.google.gwt.dev.jjs.UnifiedAst}
    * will have been provided to {@link PermutationWorkerFactory#getWorkers}
-   * method.
+   * method. The compiled PermutationResult will be returned via the
+   * <code>resultFile</code> parameter.
    * 
    * @throws TransientWorkerException if the Permutation should be tried again
    *           on another worker
    * @throws UnableToCompleteException due to a fatal error
    */
-  PermutationResult compile(TreeLogger logger, Permutation permutation)
+  void compile(TreeLogger logger, Permutation permutation,
+      FileBackedObject<PermutationResult> resultFile)
       throws TransientWorkerException, UnableToCompleteException;
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/PermutationWorkerFactory.java b/dev/core/src/com/google/gwt/dev/PermutationWorkerFactory.java
index 0322072..6c50d96 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationWorkerFactory.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationWorkerFactory.java
@@ -18,9 +18,8 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.UnifiedAst;
-import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.FileBackedObject;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -43,8 +42,6 @@
    */
   private static class Manager {
 
-    private static final Work POISON_PILL = new Work(null, null, null);
-
     private static enum Result {
       SUCCESS, FAIL, WORKER_DEATH
     }
@@ -69,8 +66,7 @@
             }
             TreeLogger logger = work.getLogger();
             try {
-              PermutationResult result = worker.compile(logger, work.getPerm());
-              Util.writeObjectAsFile(logger, work.getResultFile(), result);
+              worker.compile(logger, work.getPerm(), work.getResultFile());
               logger.log(TreeLogger.DEBUG, "Successfully compiled permutation");
               resultsQueue.put(Result.SUCCESS);
             } catch (TransientWorkerException e) {
@@ -98,6 +94,8 @@
       }
     }
 
+    private static final Work POISON_PILL = new Work(null, null, null);
+
     public static void run(TreeLogger logger, List<Work> work,
         List<PermutationWorker> workers) throws UnableToCompleteException {
       new Manager().doRun(logger, work, workers);
@@ -179,9 +177,10 @@
   private static class Work {
     private final TreeLogger logger;
     private final Permutation perm;
-    private final File resultFile;
+    private final FileBackedObject<PermutationResult> resultFile;
 
-    public Work(TreeLogger logger, Permutation perm, File resultFile) {
+    public Work(TreeLogger logger, Permutation perm,
+        FileBackedObject<PermutationResult> resultFile) {
       this.logger = logger;
       this.perm = perm;
       this.resultFile = resultFile;
@@ -195,7 +194,7 @@
       return perm;
     }
 
-    public File getResultFile() {
+    public FileBackedObject<PermutationResult> getResultFile() {
       return resultFile;
     }
   }
@@ -220,7 +219,8 @@
    * PermutationWorkersFactories.
    */
   public static void compilePermutations(TreeLogger logger,
-      Precompilation precompilation, int localWorkers, File[] resultFiles)
+      Precompilation precompilation, int localWorkers,
+      List<FileBackedObject<PermutationResult>> resultFiles)
       throws UnableToCompleteException {
     compilePermutations(logger, precompilation,
         precompilation.getPermutations(), localWorkers, resultFiles);
@@ -241,8 +241,9 @@
    */
   public static void compilePermutations(TreeLogger logger,
       Precompilation precompilation, Permutation[] permutations,
-      int localWorkers, File[] resultFiles) throws UnableToCompleteException {
-    assert permutations.length == resultFiles.length;
+      int localWorkers, List<FileBackedObject<PermutationResult>> resultFiles)
+      throws UnableToCompleteException {
+    assert permutations.length == resultFiles.size();
     assert Arrays.asList(precompilation.getPermutations()).containsAll(
         Arrays.asList(permutations));
 
@@ -252,7 +253,7 @@
       Permutation perm = permutations[i];
       TreeLogger permLogger = logger.branch(TreeLogger.DEBUG,
           "Worker permutation " + perm.getId() + " of " + permutations.length);
-      work.add(new Work(permLogger, perm, resultFiles[i]));
+      work.add(new Work(permLogger, perm, resultFiles.get(i)));
     }
 
     // Create the workers.
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index c5a4d82..c6a1c97 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -122,6 +122,10 @@
       return jjsOptions.isEnableAssertions();
     }
 
+    public boolean isSoycEnabled() {
+      return jjsOptions.isSoycEnabled();
+    }
+
     public boolean isValidateOnly() {
       return validateOnly;
     }
@@ -142,6 +146,10 @@
       jjsOptions.setOutput(output);
     }
 
+    public void setSoycEnabled(boolean enabled) {
+      jjsOptions.setSoycEnabled(enabled);
+    }
+
     public void setValidateOnly(boolean validateOnly) {
       this.validateOnly = validateOnly;
     }
diff --git a/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java b/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
index 89eebf0..fe8e467 100644
--- a/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
+++ b/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.util.FileBackedObject;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -40,11 +41,13 @@
       this.id = id;
     }
 
-    public PermutationResult compile(final TreeLogger logger,
-        final Permutation permutation) throws TransientWorkerException,
-        UnableToCompleteException {
+    public void compile(TreeLogger logger, Permutation permutation,
+        FileBackedObject<PermutationResult> resultFile)
+        throws TransientWorkerException, UnableToCompleteException {
       try {
-        return CompilePerms.compile(logger, permutation, ast);
+        PermutationResult result = CompilePerms.compile(logger, permutation,
+            ast);
+        resultFile.set(logger, result);
       } catch (OutOfMemoryError e) {
         logger.log(TreeLogger.ERROR,
             "OutOfMemoryError: Increase heap size or lower "
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
index f66bce6..5cc8bb9 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
@@ -241,6 +241,9 @@
       ModuleDefSchema schema = new ModuleDefSchema(logger, this, moduleURL,
           moduleDir, moduleDef);
       ReflectiveParser.parse(logger, schema, r);
+    } catch (Throwable e) {
+      logger.log(TreeLogger.ERROR, "Unexpected error while processing XML", e);
+      throw new UnableToCompleteException();
     } finally {
       Utility.close(r);
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/Correlation.java b/dev/core/src/com/google/gwt/dev/jjs/Correlation.java
index f077d86..0688003 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/Correlation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/Correlation.java
@@ -20,9 +20,15 @@
 import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsName;
+
+import org.apache.commons.collections.map.ReferenceMap;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumMap;
+import java.util.Map;
 
 /**
  * Each SourceInfo may define one or more axes by which it can be correlated
@@ -43,29 +49,17 @@
    */
   public enum Axis {
     /*
-     * TODO(bobv): Consider whether or not this should be a proper class
-     * hierarchy. The nice thing about an enum is that all possible types are
-     * programmatically enumerable.
-     * 
-     * Also, consider MODULE and PACKAGE values.
+     * Note to implementors: Possibly the switch statement in
+     * StandardCompilationArtifact if additional member-type enum values are
+     * added.
      */
 
     /**
-     * Represents a physical source file.
-     */
-    FILE(true, true),
-
-    /**
      * A Java class or interface type.
      */
     CLASS(true, false),
 
     /**
-     * A Java method.
-     */
-    METHOD(true, false),
-
-    /**
      * A field defined within a Java type.
      */
     FIELD(true, false),
@@ -73,7 +67,34 @@
     /**
      * A JavaScript function derived from a class or method.
      */
-    FUNCTION(false, true);
+    FUNCTION(false, true),
+
+    /**
+     * Objects with global names may be aliased (e.g. polymorphic method
+     * dispatch).
+     */
+    JS_ALIAS(false, true),
+
+    /**
+     * The globally-unique identifier used to represent the Member in the
+     * compiled output.
+     */
+    JS_NAME(false, true),
+
+    /**
+     * Indicates a literal value in the original source.
+     */
+    LITERAL(true, true),
+
+    /**
+     * A Java method.
+     */
+    METHOD(true, false),
+
+    /**
+     * Represents a physical source file.
+     */
+    ORIGIN(true, true);
 
     private final boolean isJava;
     private final boolean isJs;
@@ -96,6 +117,49 @@
   }
 
   /**
+   * Specifies the type of literal value.
+   */
+  public enum Literal {
+    VOID("void"), NULL("null"), BYTE("byte"), SHORT("short"), INT("int"), LONG(
+        "long"), FLOAT("float"), DOUBLE("double"), BOOLEAN("boolean"), CHAR(
+        "char"), STRING("string"), CLASS("class"), JS_BOOLEAN("boolean", true), JS_NUMBER(
+        "number", true), JS_NULL("null", true), JS_STRING("string", true),
+    /**
+     * undefined isn't actually a literal in JS, but we more-or-less treat it as
+     * though it were.
+     */
+    JS_UNDEFINED("undefined", true);
+
+    private final String description;
+    private final boolean isJava;
+    private final boolean isJs;
+
+    private Literal(String description) {
+      this.description = description;
+      isJava = true;
+      isJs = false;
+    }
+
+    private Literal(String description, boolean isJs) {
+      this.description = description;
+      isJava = !isJs;
+      this.isJs = isJs;
+    }
+
+    public String getDescription() {
+      return description;
+    }
+
+    public boolean isJava() {
+      return isJava;
+    }
+
+    public boolean isJs() {
+      return isJs;
+    }
+  }
+
+  /**
    * Compares Correlations based on axis and idents. Note that due to inherent
    * limitations of mapping AST nodes into Strings, this Comparator may not
    * always agree with {@link Correlation#equals(Object)}.
@@ -111,12 +175,103 @@
     }
   };
 
+  /**
+   * This cuts down on the total number of Correlation objects allocated.
+   */
+  @SuppressWarnings("unchecked")
+  private static final Map<Object, Correlation> CANONICAL_MAP = Collections.synchronizedMap(new ReferenceMap(
+      ReferenceMap.WEAK, ReferenceMap.WEAK));
+
+  /**
+   * Correlations based on Literals are all the same, so we'll just cook up a
+   * Map to make {@link #by(Literal)} fast.
+   */
+  private static final Map<Literal, Correlation> LITERAL_CORRELATIONS = new EnumMap<Literal, Correlation>(
+      Literal.class);
+
+  static {
+    for (Literal l : Literal.values()) {
+      LITERAL_CORRELATIONS.put(l, new Correlation(Axis.LITERAL,
+          l.getDescription(), l));
+    }
+  }
+
   public static Correlation by(JField field) {
-    return new Correlation(Axis.FIELD, field.getEnclosingType().getName()
-        + "::" + field.getName(), field);
+    Correlation toReturn = CANONICAL_MAP.get(field);
+    if (toReturn == null) {
+      toReturn = new Correlation(Axis.FIELD, field.getEnclosingType().getName()
+          + "::" + field.getName(), field);
+      CANONICAL_MAP.put(field, toReturn);
+    }
+    return toReturn;
   }
 
   public static Correlation by(JMethod method) {
+    Correlation toReturn = CANONICAL_MAP.get(method);
+    if (toReturn == null) {
+
+      toReturn = new Correlation(Axis.METHOD, getMethodIdent(method), method);
+      CANONICAL_MAP.put(method, toReturn);
+    }
+    return toReturn;
+  }
+
+  public static Correlation by(JReferenceType type) {
+    Correlation toReturn = CANONICAL_MAP.get(type);
+    if (toReturn == null) {
+      toReturn = new Correlation(Axis.CLASS, type.getName(), type);
+      CANONICAL_MAP.put(type, toReturn);
+    }
+    return toReturn;
+  }
+
+  public static Correlation by(JsFunction function) {
+    Correlation toReturn = CANONICAL_MAP.get(function);
+    if (toReturn == null) {
+      toReturn = new Correlation(Axis.FUNCTION, function.getName().getIdent(),
+          function);
+      CANONICAL_MAP.put(function, toReturn);
+    }
+    return toReturn;
+  }
+
+  /**
+   * Creates a JS_NAME Correlation.
+   */
+  public static Correlation by(JsName name) {
+    return by(name, false);
+  }
+
+  /**
+   * Creates either a JS_NAME or JS_ALIAS correlation, based on the value of
+   * <code>isAlias</code>.
+   */
+  public static Correlation by(JsName name, boolean isAlias) {
+    Correlation toReturn = CANONICAL_MAP.get(name);
+    if (toReturn == null) {
+      toReturn = new Correlation(isAlias ? Axis.JS_ALIAS : Axis.JS_NAME,
+          name.getIdent(), name);
+      CANONICAL_MAP.put(name, toReturn);
+    }
+    return toReturn;
+  }
+
+  public static Correlation by(Literal type) {
+    assert LITERAL_CORRELATIONS.containsKey(type);
+    return LITERAL_CORRELATIONS.get(type);
+  }
+
+  public static Correlation by(SourceOrigin origin) {
+    Correlation toReturn = CANONICAL_MAP.get(origin);
+    if (toReturn == null) {
+      toReturn = new Correlation(Axis.ORIGIN, origin.getFileName() + ":"
+          + origin.getStartLine(), origin);
+      CANONICAL_MAP.put(origin, toReturn);
+    }
+    return toReturn;
+  }
+
+  private static String getMethodIdent(JMethod method) {
     StringBuilder sb = new StringBuilder();
     sb.append(method.getEnclosingType().getName()).append("::");
     sb.append(method.getName()).append("(");
@@ -124,30 +279,13 @@
       sb.append(type.getJsniSignatureName());
     }
     sb.append(")");
-
-    return new Correlation(Axis.METHOD, sb.toString(), method);
-  }
-
-  public static Correlation by(JReferenceType type) {
-    return new Correlation(Axis.CLASS, type.getName(), type);
-  }
-
-  public static Correlation by(JsFunction function) {
-    return new Correlation(Axis.FUNCTION, function.getName().getIdent(),
-        function);
-  }
-
-  /**
-   * Constructs a {@link Axis#FILE} Correlation.
-   */
-  public static Correlation by(String filename) {
-    return new Correlation(Axis.FILE, filename, filename);
+    return sb.toString();
   }
 
   /**
    * This may contain a reference to either a Java or Js AST node.
    */
-  protected final Object astReference;
+  protected final Serializable astReference;
 
   protected final Axis axis;
 
@@ -159,9 +297,13 @@
    */
   protected final String ident;
 
-  private Correlation(Axis axis, String ident, Object astReference) {
-    if (ident == null) {
+  private Correlation(Axis axis, String ident, Serializable astReference) {
+    if (axis == null) {
+      throw new NullPointerException("axis");
+    } else if (ident == null) {
       throw new NullPointerException("ident");
+    } else if (astReference == null) {
+      throw new NullPointerException("astReference");
     }
 
     this.axis = axis;
@@ -178,7 +320,7 @@
 
     boolean astSame = astReference == c.astReference
         || (astReference != null && astReference.equals(c.astReference));
-    return axis.equals(c.axis) && astSame;
+    return axis == c.axis && astSame;
   }
 
   public Axis getAxis() {
@@ -209,6 +351,14 @@
     return ident;
   }
 
+  public Literal getLiteral() {
+    if (axis == Axis.LITERAL) {
+      return (Literal) astReference;
+    } else {
+      return null;
+    }
+  }
+
   public JMethod getMethod() {
     if (axis == Axis.METHOD) {
       return (JMethod) astReference;
@@ -217,6 +367,22 @@
     }
   }
 
+  public JsName getName() {
+    if (axis == Axis.JS_NAME || axis == Axis.JS_ALIAS) {
+      return (JsName) astReference;
+    } else {
+      return null;
+    }
+  }
+
+  public SourceOrigin getOrigin() {
+    if (axis == Axis.ORIGIN) {
+      return (SourceOrigin) astReference;
+    } else {
+      return null;
+    }
+  }
+
   public JReferenceType getType() {
     if (axis == Axis.CLASS) {
       return (JReferenceType) astReference;
@@ -231,7 +397,12 @@
 
   @Override
   public int hashCode() {
-    return 37 * axis.hashCode() + astReference.hashCode() + 13;
+    /*
+     * The null checks are because this method gets called during
+     * deserialization, but without values having been set.
+     */
+    return 37 * (axis == null ? 1 : axis.hashCode())
+        + (astReference == null ? 0 : astReference.hashCode()) + 13;
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
index 6e9e162..770e932 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -18,10 +18,11 @@
 import com.google.gwt.dev.util.arg.OptionAggressivelyOptimize;
 import com.google.gwt.dev.util.arg.OptionEnableAssertions;
 import com.google.gwt.dev.util.arg.OptionScriptStyle;
+import com.google.gwt.dev.util.arg.OptionSoycEnabled;
 
 /**
  * Controls options for the {@link JavaToJavaScriptCompiler}.
  */
 public interface JJSOptions extends OptionAggressivelyOptimize,
-    OptionEnableAssertions, OptionScriptStyle {
+    OptionEnableAssertions, OptionScriptStyle, OptionSoycEnabled {
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
index 8873c41..5001319 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -25,6 +25,7 @@
   private boolean aggressivelyOptimize = true;
   private boolean enableAssertions;
   private JsOutputOption output = JsOutputOption.OBFUSCATED;
+  private boolean soycEnabled = false;
 
   public JJSOptionsImpl() {
   }
@@ -37,6 +38,7 @@
     setAggressivelyOptimize(other.isAggressivelyOptimize());
     setEnableAssertions(other.isEnableAssertions());
     setOutput(other.getOutput());
+    setSoycEnabled(other.isSoycEnabled());
   }
 
   public JsOutputOption getOutput() {
@@ -51,6 +53,10 @@
     return enableAssertions;
   }
 
+  public boolean isSoycEnabled() {
+    return soycEnabled;
+  }
+
   public void setAggressivelyOptimize(boolean aggressivelyOptimize) {
     this.aggressivelyOptimize = aggressivelyOptimize;
   }
@@ -62,4 +68,8 @@
   public void setOutput(JsOutputOption output) {
     this.output = output;
   }
+
+  public void setSoycEnabled(boolean enabled) {
+    soycEnabled = enabled;
+  }
 }
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 2d8d57a..4d818a6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -17,6 +17,10 @@
 
 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.StandardCompilationAnalysis;
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.dev.PermutationResult;
 import com.google.gwt.dev.jdt.RebindPermutationOracle;
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
 import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
@@ -48,7 +52,6 @@
 import com.google.gwt.dev.jjs.impl.FragmentLoaderCreator;
 import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
 import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
-import com.google.gwt.dev.jjs.impl.JavaAndJavaScript;
 import com.google.gwt.dev.jjs.impl.JavaScriptObjectNormalizer;
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
 import com.google.gwt.dev.jjs.impl.JsoDevirtualizer;
@@ -70,12 +73,14 @@
 import com.google.gwt.dev.js.JsNormalizer;
 import com.google.gwt.dev.js.JsObfuscateNamer;
 import com.google.gwt.dev.js.JsPrettyNamer;
+import com.google.gwt.dev.js.JsReportGenerationVisitor;
 import com.google.gwt.dev.js.JsSourceGenerationVisitor;
 import com.google.gwt.dev.js.JsStaticEval;
 import com.google.gwt.dev.js.JsStringInterner;
 import com.google.gwt.dev.js.JsSymbolResolver;
 import com.google.gwt.dev.js.JsUnusedFunctionRemover;
 import com.google.gwt.dev.js.JsVerboseNamer;
+import com.google.gwt.dev.js.JsReportGenerationVisitor.CountingTextOutput;
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
@@ -97,11 +102,28 @@
 import java.util.TreeSet;
 
 /**
- * Compiles the Java <code>JProgram</code> representation into its
- * corresponding JavaScript source.
+ * Compiles the Java <code>JProgram</code> representation into its corresponding
+ * JavaScript source.
  */
 public class JavaToJavaScriptCompiler {
 
+  private static class PermutationResultImpl implements PermutationResult {
+    private final ArtifactSet artifacts = new ArtifactSet();
+    private final String[] js;
+
+    public PermutationResultImpl(String[] js) {
+      this.js = js;
+    }
+
+    public ArtifactSet getArtifacts() {
+      return artifacts;
+    }
+
+    public String[] getJs() {
+      return js;
+    }
+  }
+
   /**
    * Compiles a particular permutation, based on a precompiled unified AST.
    * 
@@ -114,16 +136,9 @@
    * @throws UnableToCompleteException if an error other than
    *           {@link OutOfMemoryError} occurs
    */
-  public static String[] compilePermutation(TreeLogger logger,
+  public static PermutationResult compilePermutation(TreeLogger logger,
       UnifiedAst unifiedAst, Map<String, String> rebindAnswers)
       throws UnableToCompleteException {
-    return compilePermutationToJavaAndJavaScript(logger, unifiedAst,
-        rebindAnswers).jscode;
-  }
-
-  public static JavaAndJavaScript compilePermutationToJavaAndJavaScript(
-      TreeLogger logger, UnifiedAst unifiedAst,
-      Map<String, String> rebindAnswers) throws UnableToCompleteException {
     try {
       if (JProgram.isTracingEnabled()) {
         System.out.println("------------------------------------------------------------");
@@ -222,17 +237,34 @@
 
       // (12) Generate the final output text.
       String[] js = new String[jsProgram.getFragmentCount()];
-      for (int i = 0; i < js.length; i++) {
+      List<Map<Range, SourceInfo>> sourceInfoMaps = options.isSoycEnabled()
+          ? new ArrayList<Map<Range, SourceInfo>>(jsProgram.getFragmentCount())
+          : null;
 
-        DefaultTextOutput out = new DefaultTextOutput(
-            options.getOutput().shouldMinimize());
-        JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
-        v.accept(jsProgram.getFragmentBlock(i));
-        js[i] = out.toString();
+      for (int i = 0; i < js.length; i++) {
+        if (sourceInfoMaps != null) {
+          CountingTextOutput out = new CountingTextOutput(
+              options.getOutput().shouldMinimize());
+          JsReportGenerationVisitor v = new JsReportGenerationVisitor(out);
+          v.accept(jsProgram.getFragmentBlock(i));
+          js[i] = out.toString();
+          sourceInfoMaps.add(v.getSourceInfoMap());
+        } else {
+          DefaultTextOutput out = new DefaultTextOutput(
+              options.getOutput().shouldMinimize());
+          JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
+          v.accept(jsProgram.getFragmentBlock(i));
+          js[i] = out.toString();
+        }
       }
 
-      return new JavaAndJavaScript(jprogram, jsProgram, js,
-          postStringInterningMap);
+      PermutationResult toReturn = new PermutationResultImpl(js);
+      if (sourceInfoMaps != null) {
+        toReturn.getArtifacts().add(
+            new StandardCompilationAnalysis(logger, sourceInfoMaps));
+      }
+
+      return toReturn;
     } catch (Throwable e) {
       throw logAndTranslateException(logger, e);
     }
@@ -283,8 +315,8 @@
     checkForErrors(logger, goldenCuds, false);
 
     PerfLogger.start("Build AST");
-    JProgram jprogram = new JProgram();
-    JsProgram jsProgram = new JsProgram();
+    JProgram jprogram = new JProgram(options.isSoycEnabled());
+    JsProgram jsProgram = new JsProgram(options.isSoycEnabled());
 
     try {
       /*
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
index c8d9e9b..f4381b8 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
@@ -18,15 +18,12 @@
 import com.google.gwt.dev.jjs.Correlation.Axis;
 
 import java.io.Serializable;
-import java.lang.ref.Reference;
-import java.lang.ref.SoftReference;
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.HashSet;
-import java.util.Map;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -35,6 +32,32 @@
 public class SourceInfo implements Serializable {
 
   /**
+   * Describes how the SourceInfo's node was mutated during the compile cycle.
+   */
+  public static final class Mutation implements Serializable {
+    private final String caller;
+    private final String description;
+    private final long ts = System.currentTimeMillis();
+
+    private Mutation(String description, String caller) {
+      this.caller = caller;
+      this.description = description;
+    }
+
+    public String getCaller() {
+      return caller;
+    }
+
+    public String getDescription() {
+      return description;
+    }
+
+    public long getTimestamp() {
+      return ts;
+    }
+  }
+
+  /**
    * A totally-immutable version of SourceInfo.
    */
   protected static class Immutable extends SourceInfo {
@@ -44,95 +67,93 @@
     }
 
     @Override
-    public void addAdditonalAncestors(SourceInfo... sourceInfos) {
-      throw new UnsupportedOperationException(
-          "May not add additional ancestors to the " + getFileName()
-              + " SourceInfo");
-    }
-
-    @Override
     public void addCorrelation(Correlation c) {
       throw new UnsupportedOperationException(
-          "May not add correlations to the " + getFileName() + "SourceInfo");
+          "May not add correlations to the " + getFileName()
+              + "SourceInfo. Call makeChild() first.");
     }
 
     @Override
-    public void addSupertypeAncestors(SourceInfo... sourceInfos) {
+    public void copyMissingCorrelationsFrom(SourceInfo other) {
       throw new UnsupportedOperationException(
-          "May not add supertype ancestors to the " + getFileName()
-              + " SourceInfo");
+          "May not copy correlations into the " + getFileName()
+              + "SourceInfo. Call makeChild() first.");
+    }
+
+    @Override
+    public void merge(SourceInfo... sourceInfos) {
+      if (sourceInfos.length > 0) {
+        throw new UnsupportedOperationException(
+            "May not merge SourceInfos into the " + getFileName()
+                + " SourceInfo. Call makeChild() first.");
+      }
     }
   }
 
   /**
-   * Compares SourceInfos by their file and position information.
-   */
-  public static final Comparator<SourceInfo> LOCATION_COMPARATOR = new Comparator<SourceInfo>() {
-    public int compare(SourceInfo q, SourceInfo b) {
-      int a = q.getFileName().compareTo(b.getFileName());
-      if (a != 0) {
-        return a;
-      }
-
-      a = q.startPos - q.startPos;
-      if (a != 0) {
-        return a;
-      }
-
-      a = q.endPos - b.endPos;
-      if (a != 0) {
-        return a;
-      }
-
-      a = q.startLine - b.startLine;
-      if (a != 0) {
-        return a;
-      }
-
-      return 0;
-    }
-  };
-
-  /**
    * Indicates that the source for an AST element is unknown. This indicates a
    * deficiency in the compiler.
    */
   public static final SourceInfo UNKNOWN = new Immutable(0, 0, 0,
       "Unknown source", true);
 
-  private static final SourceInfo[] EMPTY_SOURCEINFO_ARRAY = new SourceInfo[0];
-  private final Set<SourceInfo> additionalAncestors = new HashSet<SourceInfo>();
-  private final String caller;
-  private final EnumMap<Axis, Correlation> correlations = new EnumMap<Axis, Correlation>(
-      Axis.class);
   /**
-   * This flag controls the behavior of {@link #makeChild}.
+   * Collecting mutation data is expensive in terms of additional objects and
+   * string literals and only of interest to compiler hackers, so we'll just
+   * normally have it disabled.
    */
-  private final boolean createDescendants;
-  private final int endPos;
-  private transient Reference<Set<SourceInfo>> lazyRoots;
-  private final String mutation;
-  private final SourceInfo parent;
-  private final int startLine;
-  private final int startPos;
+  private static final boolean COLLECT_MUTATIONS = Boolean.getBoolean("gwt.soyc.collectMutations");
 
-  private final Set<SourceInfo> supertypeAncestors = new HashSet<SourceInfo>();
+  /**
+   * Micro-opt for {@link #makeChild(Class, String)}.
+   */
+  private static final SourceInfo[] EMPTY_SOURCEINFO_ARRAY = new SourceInfo[0];
+
+  /**
+   * This flag controls the behavior of the mutable methods to make them no-ops.
+   */
+  private final boolean accumulateData;
+
+  /**
+   * Any Correlation associated with the SourceInfo.
+   */
+  private final Set<Correlation> allCorrelations;
+
+  /**
+   * Holds Mutation objects if the compiler is configured to collect mutations.
+   */
+  private final List<Mutation> mutations = COLLECT_MUTATIONS
+      ? new ArrayList<Mutation>() : null;
+
+  /**
+   * Holds the origin data for the SourceInfo.
+   */
+  private final SourceOrigin origin;
+
+  /**
+   * Records the first Correlation on any given Axis applied to the SourceInfo.
+   */
+  private final EnumMap<Axis, Correlation> primaryCorrelations;
 
   protected SourceInfo(int startPos, int endPos, int startLine,
-      String fileName, boolean createDescendants) {
+      String fileName, boolean accumulateData) {
     assert fileName != null;
 
-    this.createDescendants = createDescendants;
-    this.startPos = startPos;
-    this.endPos = endPos;
-    this.startLine = startLine;
-    this.parent = null;
-    this.mutation = null;
-    this.caller = null;
+    this.accumulateData = accumulateData;
+    origin = SourceOrigin.create(startPos, endPos, startLine, fileName);
 
-    // Don't use addCorrelation because of the immutable subclasses
-    Correlation file = Correlation.by(fileName);
-    correlations.put(file.getAxis(), file);
+    // Be very aggressive in not allocating collections that we don't need.
+    if (accumulateData) {
+      allCorrelations = new HashSet<Correlation>();
+      primaryCorrelations = new EnumMap<Axis, Correlation>(Axis.class);
+      // Don't use addCorrelation because of the immutable subclasses
+      Correlation originCorrelation = Correlation.by(origin);
+      allCorrelations.add(originCorrelation);
+      primaryCorrelations.put(Axis.ORIGIN, originCorrelation);
+    } else {
+      allCorrelations = null;
+      primaryCorrelations = null;
+    }
   }
 
   private SourceInfo(SourceInfo parent, String mutation, String caller,
@@ -141,69 +162,60 @@
     assert mutation != null;
     assert caller != null;
 
-    this.createDescendants = parent.createDescendants;
-    this.startPos = parent.startPos;
-    this.endPos = parent.endPos;
-    this.startLine = parent.startLine;
-    this.additionalAncestors.addAll(Arrays.asList(additionalAncestors));
-    this.additionalAncestors.addAll(parent.additionalAncestors);
-    this.parent = parent;
-    this.mutation = mutation;
-    this.caller = caller;
-  }
+    this.accumulateData = parent.accumulateData;
+    this.origin = parent.origin;
 
-  /**
-   * Add additional ancestor SourceInfos. These SourceInfo objects indicate that
-   * a merge-type operation took place or that the additional ancestors have a
-   * containment relationship with the SourceInfo.
-   */
-  public void addAdditonalAncestors(SourceInfo... sourceInfos) {
-    if (!createDescendants) {
-      return;
+    if (accumulateData) {
+      this.allCorrelations = new HashSet<Correlation>(parent.allCorrelations);
+      this.primaryCorrelations = new EnumMap<Axis, Correlation>(
+          parent.primaryCorrelations);
+    } else {
+      allCorrelations = null;
+      primaryCorrelations = null;
     }
 
-    additionalAncestors.addAll(Arrays.asList(sourceInfos));
-    additionalAncestors.remove(this);
-
-    if (lazyRoots != null) {
-      lazyRoots.clear();
+    if (COLLECT_MUTATIONS) {
+      this.mutations.addAll(parent.mutations);
+      this.mutations.add(new Mutation(mutation, caller));
     }
+
+    merge(additionalAncestors);
   }
 
   /**
    * Add a Correlation to the SourceInfo.
-   * 
-   * @throws IllegalArgumentException if a Correlation with the same Axis had
-   *           been previously added to the SourceInfo. The reason for this is
-   *           that a Correlation shouldn't be re-applied to the same SourceInfo
-   *           node, if this were done, the caller should have also called
-   *           makeChild() first, since something interesting is gong on.
    */
   public void addCorrelation(Correlation c) {
-    if (!createDescendants) {
+    if (!accumulateData) {
       return;
     }
 
-    Axis axis = c.getAxis();
+    allCorrelations.add(c);
 
-    if (correlations.containsKey(axis)) {
-      throw new IllegalArgumentException("Correlation on axis " + axis
-          + " has already been added. Call makeChild() first.");
+    if (!primaryCorrelations.containsKey(c.getAxis())) {
+      primaryCorrelations.put(c.getAxis(), c);
     }
-
-    correlations.put(axis, c);
   }
 
   /**
-   * Add SourceInfos that indicate the supertype derivation.
+   * Copy any Correlations from another SourceInfo node if there are no
+   * Correlations on this SourceInfo with the same Axis.
    */
-  public void addSupertypeAncestors(SourceInfo... sourceInfos) {
-    if (!createDescendants) {
+  public void copyMissingCorrelationsFrom(SourceInfo other) {
+    if (!accumulateData) {
       return;
     }
 
-    supertypeAncestors.addAll(Arrays.asList(sourceInfos));
-    supertypeAncestors.remove(this);
+    EnumSet<Axis> toAdd = EnumSet.allOf(Axis.class);
+    for (Correlation c : allCorrelations) {
+      toAdd.remove(c.getAxis());
+    }
+
+    for (Correlation c : other.getAllCorrelations()) {
+      if (toAdd.contains(c.getAxis())) {
+        addCorrelation(c);
+      }
+    }
   }
 
   /**
@@ -211,16 +223,8 @@
    * ancestor SourceInfo, and any supertype SourceInfos.
    */
   public Set<Correlation> getAllCorrelations() {
-    EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
-        Axis.class);
-    findCorrelations(accumulator, EnumSet.allOf(Axis.class), false);
-
-    Set<Correlation> toReturn = new HashSet<Correlation>();
-    for (Set<Correlation> toAdd : accumulator.values()) {
-      toReturn.addAll(toAdd);
-    }
-
-    return Collections.unmodifiableSet(toReturn);
+    return accumulateData ? allCorrelations
+        : Collections.<Correlation> emptySet();
   }
 
   /**
@@ -228,253 +232,123 @@
    * parent, additional ancestor SourceInfo, and any supertype SourceInfos.
    */
   public Set<Correlation> getAllCorrelations(Axis axis) {
-    EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
-        Axis.class);
-    findCorrelations(accumulator, EnumSet.of(axis), false);
-    assert accumulator.size() < 2;
-    if (accumulator.size() == 0) {
-      return Collections.unmodifiableSet(new HashSet<Correlation>());
-    } else {
-      assert accumulator.containsKey(axis);
-      assert accumulator.get(axis).size() > 0;
-      return Collections.unmodifiableSet(accumulator.get(axis));
-    }
-  }
-
-  public int getEndPos() {
-    return endPos;
-  }
-
-  public String getFileName() {
-    return getPrimaryCorrelation(Axis.FILE).getIdent();
-  }
-
-  /**
-   * Return the most-derived Correlation along a given Axis or <code>null</code>
-   * if no such correlation exists. The search path uses the current SourceInfo,
-   * parent chain, and additional ancestors, but not supertype SourceInfos.
-   */
-  public Correlation getPrimaryCorrelation(Axis axis) {
-    EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
-        Axis.class);
-    findCorrelations(accumulator, EnumSet.of(axis), true);
-    assert accumulator.size() < 2;
-    if (accumulator.size() == 0) {
-      return null;
-    } else {
-      assert accumulator.containsKey(axis);
-      assert accumulator.get(axis).size() == 1;
-      return accumulator.get(axis).iterator().next();
-    }
-  }
-
-  /**
-   * Returns the most-derived Correlations along each Axis on which a
-   * Correlation has been set. The search path uses the current SourceInfo,
-   * parent chain, and additional ancestors, but not supertype SourceInfos.
-   */
-  public Set<Correlation> getPrimaryCorrelations() {
-    EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
-        Axis.class);
-    findCorrelations(accumulator, EnumSet.allOf(Axis.class), true);
-
-    EnumMap<Axis, Correlation> toReturn = new EnumMap<Axis, Correlation>(
-        Axis.class);
-    for (Map.Entry<Axis, Set<Correlation>> entry : accumulator.entrySet()) {
-      assert entry.getValue().size() == 1;
-      toReturn.put(entry.getKey(), entry.getValue().iterator().next());
+    if (!accumulateData) {
+      return Collections.emptySet();
     }
 
-    return Collections.unmodifiableSet(new HashSet<Correlation>(
-        toReturn.values()));
-  }
-
-  /**
-   * Returns the SourceInfos from which this SourceInfo was ultimately derived.
-   * SourceInfo objects which were not derived from others, via
-   * {@link #makeChild}, will list itself as its root SourceInfo.
-   */
-  public Set<SourceInfo> getRoots() {
-    if (parent == null && additionalAncestors.isEmpty()) {
-      // If parent is null, we shouldn't have additional ancestors
-      return Collections.unmodifiableSet(new HashSet<SourceInfo>(
-          Collections.singleton(this)));
-
-    } else if (additionalAncestors.size() == 0) {
-      // This is a fairly typical case, where a node only has a parent
-      return parent.getRoots();
+    Set<Correlation> toReturn = new HashSet<Correlation>();
+    for (Correlation c : getAllCorrelations()) {
+      if (c.getAxis() == axis) {
+        toReturn.add(c);
+      }
     }
-
-    // See if previously-computed work is available
-    Set<SourceInfo> roots;
-    if (lazyRoots != null && (roots = lazyRoots.get()) != null) {
-      return roots;
-    }
-
-    // Otherwise, do some actual work
-    roots = new HashSet<SourceInfo>();
-
-    if (parent == null) {
-      roots.add(this);
-    } else {
-      roots.addAll(parent.getRoots());
-    }
-
-    for (SourceInfo ancestor : additionalAncestors) {
-      roots.addAll(ancestor.getRoots());
-    }
-
-    Set<SourceInfo> toReturn = Collections.unmodifiableSet(roots);
-    lazyRoots = new SoftReference<Set<SourceInfo>>(toReturn);
     return toReturn;
   }
 
+  public int getEndPos() {
+    return getOrigin().getEndPos();
+  }
+
+  public String getFileName() {
+    return getOrigin().getFileName();
+  }
+
+  /**
+   * Returns a summary of the mutations applied to the SourceInfo. It it
+   * expensive to collect mutation data, so this method will only return useful
+   * values if the <code>gwt.jjs.collectMutations</code> system property is
+   * defined.
+   */
+  public List<Mutation> getMutations() {
+    if (COLLECT_MUTATIONS) {
+      return mutations;
+    } else {
+      return Collections.emptyList();
+    }
+  }
+
+  public SourceOrigin getOrigin() {
+    return origin;
+  }
+
+  /**
+   * Returns the first Correlation that had been set with a given Axis, or
+   * <code>null</code> if no Correlation has been set on the given axis.
+   */
+  public Correlation getPrimaryCorrelation(Axis axis) {
+    return accumulateData ? primaryCorrelations.get(axis) : null;
+  }
+
+  /**
+   * Returns the first Correlations added along each Axis on which a Correlation
+   * has been set.
+   */
+  public Set<Correlation> getPrimaryCorrelations() {
+    return accumulateData ? new HashSet<Correlation>(
+        primaryCorrelations.values()) : Collections.<Correlation> emptySet();
+  }
+
   public int getStartLine() {
-    return startLine;
+    return getOrigin().getStartLine();
   }
 
   public int getStartPos() {
-    return startPos;
-  }
-
-  public String getStory() {
-    /*
-     * TODO(bobv): This is a temporary implementation. At some point it should
-     * return a proper object which tools could use.
-     */
-    StringBuilder toReturn = new StringBuilder();
-
-    if (caller != null) {
-      SourceInfo si = this;
-      while (si != null) {
-        if (si.mutation != null) {
-          toReturn.append(si.mutation + " by " + si.caller + "\n  ");
-        }
-        si = si.parent;
-      }
-    }
-
-    Set<SourceInfo> roots = getRoots();
-    if (!roots.isEmpty()) {
-      toReturn.append("\nRoots:\n");
-      for (SourceInfo root : roots) {
-        toReturn.append("  " + root.getFileName() + ":" + root.getStartLine()
-            + "\n");
-      }
-    }
-
-    Set<Correlation> allCorrelations = getAllCorrelations();
-    Set<Correlation> primaryCorrelations = getPrimaryCorrelations();
-    if (!allCorrelations.isEmpty()) {
-      toReturn.append("\nCorrelations:\n");
-      for (Correlation c : allCorrelations) {
-        toReturn.append((primaryCorrelations.contains(c) ? " *" : "  ") + c
-            + "\n");
-      }
-    }
-
-    Set<SourceInfo> supertypes = supertypeAncestors;
-    if (!supertypes.isEmpty()) {
-      toReturn.append("\nSupertypes:\n{\n");
-      for (SourceInfo info : supertypes) {
-        toReturn.append(info.getStory());
-      }
-      toReturn.append("\n}\n");
-    }
-
-    return toReturn.toString();
+    return getOrigin().getStartPos();
   }
 
   /**
-   * Returns <code>true</code> if {@link #getAllCorrelations()} would return a
-   * Correlation that has one or more of the specifies Axes.
-   */
-  public boolean hasCorrelation(Set<Axis> axes) {
-    // Try local information
-    if (!correlations.isEmpty()) {
-      for (Axis a : axes) {
-        if (correlations.containsKey(a)) {
-          return true;
-        }
-      }
-    }
-
-    // Try the parent chain
-    if (parent != null && parent.hasCorrelation(axes)) {
-      return true;
-    }
-
-    // Try additional ancestors
-    for (SourceInfo info : additionalAncestors) {
-      if (info.hasCorrelation(axes)) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  /**
-   * Create a derived SourceInfo object. If SourceInfo collection is disabled,
-   * this method will return the current object.
+   * If data accumulation is enabled, create a derived SourceInfo object that
+   * indicates that one or more AST nodes were merged to create a new node. The
+   * derived node will inherit its Origin and Correlations from the SourceInfo
+   * object on which the method is invoked.
    */
   public SourceInfo makeChild(Class<?> caller, String description) {
-    return makeChild(caller, description, EMPTY_SOURCEINFO_ARRAY);
+    return accumulateData ? makeChild(caller, description,
+        EMPTY_SOURCEINFO_ARRAY) : this;
   }
 
   /**
-   * Create a derived SourceInfo object that indicates that one or more AST
-   * nodes were merged to create a new node. The derived node will inherit its
-   * location from the SourceInfo object on which the method is invoked.
+   * If data accumulation is enabled, create a derived SourceInfo object that
+   * indicates that one or more AST nodes were merged to create a new node. The
+   * derived node will inherit its Origin and Correlations from the SourceInfo
+   * object on which the method is invoked.
    */
   public SourceInfo makeChild(Class<?> caller, String description,
-      SourceInfo... additionalAncestors) {
-    if (!createDescendants) {
+      SourceInfo... merge) {
+    if (!accumulateData) {
       return this;
     }
 
     String callerName = caller == null ? "Unrecorded caller" : caller.getName();
-    return new SourceInfo(this, description, callerName, additionalAncestors);
+    return new SourceInfo(this, description, callerName, merge);
   }
 
   /**
-   * Implementation of the various getCorrelations functions.
+   * Add additional ancestor SourceInfos. These SourceInfo objects indicate that
+   * a merge-type operation took place or that the additional ancestors have a
+   * containment relationship with the SourceInfo.
    */
-  private void findCorrelations(EnumMap<Axis, Set<Correlation>> accumulator,
-      EnumSet<Axis> filter, boolean derivedOnly) {
-    // Short circuit if all possible values have been seen
-    if (derivedOnly && accumulator.size() == filter.size()) {
+  public void merge(SourceInfo... sourceInfos) {
+    if (!accumulateData) {
       return;
     }
 
-    for (Map.Entry<Axis, Correlation> entry : correlations.entrySet()) {
-      Axis key = entry.getKey();
-      boolean containsKey = accumulator.containsKey(key);
-      Correlation value = entry.getValue();
-
-      if (containsKey) {
-        if (!derivedOnly) {
-          accumulator.get(key).add(value);
-        }
-      } else if (filter.contains(key)) {
-        Set<Correlation> set = new HashSet<Correlation>();
-        set.add(value);
-        accumulator.put(key, derivedOnly ? Collections.unmodifiableSet(set)
-            : set);
+    for (SourceInfo info : sourceInfos) {
+      if (this == info || !info.accumulateData) {
+        continue;
       }
-    }
 
-    if (parent != null) {
-      parent.findCorrelations(accumulator, filter, derivedOnly);
-    }
+      allCorrelations.addAll(info.getAllCorrelations());
 
-    for (SourceInfo info : additionalAncestors) {
-      info.findCorrelations(accumulator, filter, derivedOnly);
-    }
+      if (primaryCorrelations.size() < Axis.values().length) {
+        EnumMap<Axis, Correlation> copy = new EnumMap<Axis, Correlation>(
+            info.primaryCorrelations);
+        copy.keySet().removeAll(primaryCorrelations.keySet());
+        primaryCorrelations.putAll(copy);
+      }
 
-    if (!derivedOnly) {
-      for (SourceInfo info : supertypeAncestors) {
-        info.findCorrelations(accumulator, filter, derivedOnly);
+      if (COLLECT_MUTATIONS) {
+        mutations.addAll(0, info.getMutations());
       }
     }
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
new file mode 100644
index 0000000..0b57c9f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs;
+
+import org.apache.commons.collections.map.ReferenceMap;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Describes where a SourceInfo's node came from. This class currently includes
+ * only physical origin information, but could be extended to provide support
+ * for source-Module and -Generators.
+ */
+public final class SourceOrigin implements Serializable {
+  /**
+   * This is synchronized since several threads could operate on it at once
+   * during parallel optimization phases.
+   */
+  @SuppressWarnings("unchecked")
+  private static final Map<SourceOrigin, SourceOrigin> CANONICAL_SOURCE_ORIGINS = Collections.synchronizedMap(new ReferenceMap(
+      ReferenceMap.WEAK, ReferenceMap.SOFT));
+
+  /**
+   * Creates SourceOrigin nodes. This factory method will attempt to provide
+   * canonicalized instances of SourceOrigin objects.
+   */
+  public static SourceOrigin create(int startPos, int endPos, int startLine,
+      String fileName) {
+
+    SourceOrigin newInstance = new SourceOrigin(fileName, startLine, startPos,
+        endPos);
+    SourceOrigin canonical = CANONICAL_SOURCE_ORIGINS.get(newInstance);
+
+    assert canonical == null
+        || (newInstance != canonical && newInstance.equals(canonical));
+
+    if (canonical != null) {
+      return canonical;
+    } else {
+      CANONICAL_SOURCE_ORIGINS.put(newInstance, newInstance);
+      return newInstance;
+    }
+  }
+
+  // TODO: Add Module and Generator tracking
+  private final int endPos;
+  private final String fileName;
+  private final int hash;
+  private final int startLine;
+  private final int startPos;
+
+  private SourceOrigin(String location, int startLine, int startPos, int endPos) {
+    this.fileName = location;
+    this.startLine = startLine;
+    this.startPos = startPos;
+    this.endPos = endPos;
+
+    // Go ahead and compute the hash, since it'll be used for canonicalization
+    this.hash = 13 * endPos + 17 * fileName.hashCode() + 29 * startLine + 31
+        * startPos + 2;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof SourceOrigin)) {
+      return false;
+    }
+    SourceOrigin other = (SourceOrigin) o;
+    return endPos == other.endPos && fileName.equals(other.fileName)
+        && startLine == other.startLine && startPos == other.startPos;
+  }
+
+  public int getEndPos() {
+    return endPos;
+  }
+
+  public String getFileName() {
+    return fileName;
+  }
+
+  public int getStartLine() {
+    return startLine;
+  }
+
+  public int getStartPos() {
+    return startPos;
+  }
+
+  @Override
+  public int hashCode() {
+    return hash;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 0ef2689..081bc6a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -15,8 +15,10 @@
  */
 package com.google.gwt.dev.jjs.ast;
 
+import com.google.gwt.dev.jjs.Correlation;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.Correlation.Literal;
 import com.google.gwt.dev.jjs.ast.JField.Disposition;
 import com.google.gwt.dev.jjs.ast.js.JClassSeed;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
@@ -138,6 +140,15 @@
     return traceMethods.size() > 0;
   }
 
+  /**
+   * This method is used to create SourceInfos for fields in JProgram. This
+   * method always creates a SourceInfo that has collection enabled.
+   */
+  private static SourceInfo createSourceInfoEnabled(String description) {
+    return new SourceInfoJava(-1, -1, 0, JProgram.class.getName(), true).makeChild(
+        JProgram.class, description);
+  }
+
   private static String dotify(char[][] name) {
     StringBuffer result = new StringBuffer();
     for (int i = 0; i < name.length; ++i) {
@@ -181,7 +192,7 @@
    */
   private final ArrayList<HashMap<JType, JArrayType>> dimensions = new ArrayList<HashMap<JType, JArrayType>>();
 
-  private boolean enableSourceInfoDescendants;
+  private final boolean enableSourceInfoDescendants;
 
   private final Map<String, JField> indexedFields = new HashMap<String, JField>();
 
@@ -194,25 +205,25 @@
   private List<JsonObject> jsonTypeTable;
 
   private final JAbsentArrayDimension literalAbsentArrayDim = new JAbsentArrayDimension(
-      this, createSourceInfoSynthetic(JProgram.class, "Absent array dimension"));
+      this, createSourceInfoEnabled("Absent array dimension"));
 
   private final JBooleanLiteral literalFalse = new JBooleanLiteral(this,
-      createSourceInfoSynthetic(JProgram.class, "false literal"), false);
+      createSourceInfoEnabled("false literal"), false);
 
   private final JIntLiteral literalIntNegOne = new JIntLiteral(this,
-      createSourceInfoSynthetic(JProgram.class, "-1 literal"), -1);
+      createSourceInfoEnabled("-1 literal"), -1);
 
   private final JIntLiteral literalIntOne = new JIntLiteral(this,
-      createSourceInfoSynthetic(JProgram.class, "1 literal"), 1);
+      createSourceInfoEnabled("1 literal"), 1);
 
   private final JIntLiteral literalIntZero = new JIntLiteral(this,
-      createSourceInfoSynthetic(JProgram.class, "0 literal"), 0);
+      createSourceInfoEnabled("0 literal"), 0);
 
   private final JNullLiteral literalNull = new JNullLiteral(this,
-      createSourceInfoSynthetic(JProgram.class, "null literal"));
+      createSourceInfoEnabled("null literal"));
 
   private final JBooleanLiteral literalTrue = new JBooleanLiteral(this,
-      createSourceInfoSynthetic(JProgram.class, "true literal"), true);
+      createSourceInfoEnabled("true literal"), true);
 
   private JField nullField;
 
@@ -259,7 +270,7 @@
   private final Map<String, JReferenceType> typeNameMap = new HashMap<String, JReferenceType>();
 
   private final JNullType typeNull = new JNullType(this,
-      createSourceInfoSynthetic(JProgram.class, "null type"));
+      createSourceInfoEnabled("null type"));
 
   private final JPrimitiveType typeShort = new JPrimitiveType(this, "short",
       "S", "java.lang.Short", literalIntZero);
@@ -273,9 +284,36 @@
   private final JPrimitiveType typeVoid = new JPrimitiveType(this, "void", "V",
       "java.lang.Void", null);
 
+  private final SourceInfo stringPoolSourceInfo;
+
+  private final Map<String, JStringLiteral> stringLiteralMap = new HashMap<String, JStringLiteral>();
+
   public JProgram() {
+    this(false);
+  }
+
+  /**
+   * Constructor.
+   * 
+   * @param enableSourceInfoDescendants Controls whether or not SourceInfo nodes
+   *          created via the JProgram will record descendant information.
+   *          Enabling this feature will collect extra data during the
+   *          compilation cycle, but at a cost of memory and object allocations.
+   */
+  public JProgram(boolean enableSourceInfoDescendants) {
     super(null, SourceInfoJava.INTRINSIC.makeChild(JProgram.class,
         "Top-level program"));
+
+    this.enableSourceInfoDescendants = enableSourceInfoDescendants;
+    literalFalse.getSourceInfo().addCorrelation(Correlation.by(Literal.BOOLEAN));
+    literalIntNegOne.getSourceInfo().addCorrelation(Correlation.by(Literal.INT));
+    literalIntOne.getSourceInfo().addCorrelation(Correlation.by(Literal.INT));
+    literalIntZero.getSourceInfo().addCorrelation(Correlation.by(Literal.INT));
+    literalNull.getSourceInfo().addCorrelation(Correlation.by(Literal.NULL));
+    literalTrue.getSourceInfo().addCorrelation(Correlation.by(Literal.BOOLEAN));
+    stringPoolSourceInfo = createSourceInfoSynthetic(JProgram.class,
+        "String pool");
+    stringPoolSourceInfo.addCorrelation(Correlation.by(Literal.STRING));
   }
 
   public void addEntryMethod(JMethod entryPoint) {
@@ -581,8 +619,9 @@
 
   public JCharLiteral getLiteralChar(char c) {
     // could be interned
-    return new JCharLiteral(this, createSourceInfoSynthetic(JProgram.class, c
-        + " literal"), c);
+    SourceInfo info = createSourceInfoSynthetic(JProgram.class, c + " literal");
+    info.addCorrelation(Correlation.by(Literal.CHAR));
+    return new JCharLiteral(this, info, c);
   }
 
   /**
@@ -620,8 +659,10 @@
           0).getBody();
       clinitBody.getStatements().add(decl);
 
-      classLiteral = new JClassLiteral(this, createSourceInfoSynthetic(
-          JProgram.class, "class literal for " + type.getName()), type, field);
+      SourceInfo literalInfo = createSourceInfoSynthetic(JProgram.class,
+          "class literal for " + type.getName());
+      literalInfo.addCorrelation(Correlation.by(Literal.CLASS));
+      classLiteral = new JClassLiteral(this, literalInfo, type, field);
       classLiterals.put(type, classLiteral);
     } else {
       // Make sure the field hasn't been pruned.
@@ -647,14 +688,16 @@
 
   public JDoubleLiteral getLiteralDouble(double d) {
     // could be interned
-    return new JDoubleLiteral(this, createSourceInfoSynthetic(JProgram.class, d
-        + " literal"), d);
+    SourceInfo info = createSourceInfoSynthetic(JProgram.class, d + " literal");
+    info.addCorrelation(Correlation.by(Literal.DOUBLE));
+    return new JDoubleLiteral(this, info, d);
   }
 
   public JFloatLiteral getLiteralFloat(float f) {
     // could be interned
-    return new JFloatLiteral(this, createSourceInfoSynthetic(JProgram.class, f
-        + " literal"), f);
+    SourceInfo info = createSourceInfoSynthetic(JProgram.class, f + " literal");
+    info.addCorrelation(Correlation.by(Literal.FLOAT));
+    return new JFloatLiteral(this, info, f);
   }
 
   public JIntLiteral getLiteralInt(int i) {
@@ -665,16 +708,20 @@
         return literalIntZero;
       case 1:
         return literalIntOne;
-      default:
+      default: {
         // could be interned
-        return new JIntLiteral(this, createSourceInfoSynthetic(JProgram.class,
-            i + " literal"), i);
+        SourceInfo info = createSourceInfoSynthetic(JProgram.class, i
+            + " literal");
+        info.addCorrelation(Correlation.by(Literal.INT));
+        return new JIntLiteral(this, info, i);
+      }
     }
   }
 
   public JLongLiteral getLiteralLong(long l) {
-    return new JLongLiteral(this, createSourceInfoSynthetic(JProgram.class, l
-        + " literal"), l);
+    SourceInfo info = createSourceInfoSynthetic(JProgram.class, l + " literal");
+    info.addCorrelation(Correlation.by(Literal.LONG));
+    return new JLongLiteral(this, info, l);
   }
 
   public JNullLiteral getLiteralNull() {
@@ -682,13 +729,18 @@
   }
 
   public JStringLiteral getLiteralString(SourceInfo sourceInfo, char[] s) {
-    // should consolidate so we can build a string table in output code later?
-    return new JStringLiteral(this, sourceInfo, String.valueOf(s));
+    return getLiteralString(sourceInfo, String.valueOf(s));
   }
 
   public JStringLiteral getLiteralString(SourceInfo sourceInfo, String s) {
-    // should consolidate so we can build a string table in output code later?
-    return new JStringLiteral(this, sourceInfo, s);
+    JStringLiteral toReturn = stringLiteralMap.get(s);
+    if (toReturn == null) {
+      toReturn = new JStringLiteral(this, stringPoolSourceInfo.makeChild(
+          JProgram.class, "String literal: " + s), s);
+      stringLiteralMap.put(s, toReturn);
+    }
+    toReturn.getSourceInfo().merge(sourceInfo);
+    return toReturn;
   }
 
   public JField getNullField() {
@@ -863,16 +915,6 @@
   }
 
   /**
-   * Controls whether or not SourceInfo nodes created via the JProgram will
-   * record descendant information. Enabling this feature will collect extra
-   * data during the compilation cycle, but at a cost of memory and object
-   * allocations.
-   */
-  public void setEnableSourceInfoDescendants(boolean enable) {
-    enableSourceInfoDescendants = enable;
-  }
-
-  /**
    * If <code>method</code> is a static impl method, returns the instance
    * method that <code>method</code> is the implementation of. Otherwise,
    * returns <code>null</code>.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 7771dfe..99f9121 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -512,7 +512,7 @@
 
       // The SourceInfo will inherit Correlations from its enclosing object
       if (enclosing != null) {
-        toReturn.addAdditonalAncestors(enclosing.getSourceInfo());
+        toReturn.copyMissingCorrelationsFrom(enclosing.getSourceInfo());
       }
 
       return toReturn;
@@ -526,7 +526,7 @@
 
       // The SourceInfo will inherit Correlations from its enclosing object
       if (enclosing != null) {
-        toReturn.addAdditonalAncestors(enclosing.getSourceInfo());
+        toReturn.copyMissingCorrelationsFrom(enclosing.getSourceInfo());
       }
 
       return toReturn;
@@ -617,7 +617,6 @@
           assert (binding.superclass().isClass() || binding.superclass().isEnum());
           JClassType superClass = (JClassType) typeMap.get(superClassBinding);
           type.extnds = superClass;
-          type.getSourceInfo().addSupertypeAncestors(superClass.getSourceInfo());
         }
 
         ReferenceBinding[] superInterfaces = binding.superInterfaces();
@@ -626,8 +625,6 @@
           assert (superInterfaceBinding.isInterface());
           JInterfaceType superInterface = (JInterfaceType) typeMap.get(superInterfaceBinding);
           type.implments.add(superInterface);
-          type.getSourceInfo().addSupertypeAncestors(
-              superInterface.getSourceInfo());
         }
 
         if (type instanceof JEnumType) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index d3cbdfb..0b896dd 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
-import com.google.gwt.dev.jjs.Correlation;
 import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
@@ -2246,10 +2245,10 @@
       SourceInfo toReturn = program.createSourceInfo(x.sourceStart,
           x.sourceEnd, startLine, currentFileName);
       if (currentClass != null) {
-        toReturn.addCorrelation(Correlation.by(currentClass));
+        toReturn.copyMissingCorrelationsFrom(currentClass.getSourceInfo());
       }
       if (currentMethod != null) {
-        toReturn.addCorrelation(Correlation.by(currentMethod));
+        toReturn.copyMissingCorrelationsFrom(currentMethod.getSourceInfo());
       }
       return toReturn;
     }
@@ -2421,7 +2420,6 @@
 
           if (!method.overrides.contains(upRef)) {
             method.overrides.add(upRef);
-            method.getSourceInfo().addSupertypeAncestors(upRef.getSourceInfo());
             break;
           }
         }
@@ -2452,8 +2450,6 @@
             JMethod upRef = (JMethod) typeMap.get(tryMethod);
             if (!method.overrides.contains(upRef)) {
               method.overrides.add(upRef);
-              method.getSourceInfo().addSupertypeAncestors(
-                  upRef.getSourceInfo());
               break;
             }
           }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index a05cb76..44d322a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -162,7 +162,9 @@
       String name = x.getName();
       String mangleName = mangleName(x);
       if (x.isStatic()) {
-        names.put(x, topScope.declareName(mangleName, name));
+        JsName jsName = topScope.declareName(mangleName, name);
+        x.getSourceInfo().addCorrelation(Correlation.by(jsName));
+        names.put(x, jsName);
       } else {
         JsName jsName;
         if (x == arrayLengthField) {
@@ -174,6 +176,7 @@
         } else {
           jsName = peek().declareName(mangleName, name);
         }
+        x.getSourceInfo().addCorrelation(Correlation.by(jsName));
         names.put(x, jsName);
       }
     }
@@ -239,7 +242,9 @@
       }
 
       // My seed function name
-      names.put(x, topScope.declareName(getNameString(x), x.getShortName()));
+      JsName jsName = topScope.declareName(getNameString(x), x.getShortName());
+      x.getSourceInfo().addCorrelation(Correlation.by(jsName));
+      names.put(x, jsName);
 
       // My class scope
       if (x.extnds == null) {
@@ -289,6 +294,8 @@
           } else {
             polyName = interfaceScope.declareName(mangleName, name);
           }
+          // Record this as an alias, not the primary name
+          x.getSourceInfo().addCorrelation(Correlation.by(polyName, true));
           polymorphicNames.put(x, polyName);
         }
       }
@@ -304,6 +311,7 @@
       assert x.getEnclosingType() != null;
       String mangleName = mangleNameForGlobal(x);
       globalName = topScope.declareName(mangleName, name);
+      x.getSourceInfo().addCorrelation(Correlation.by(globalName));
       names.put(x, globalName);
 
       JsFunction jsFunction;
@@ -320,6 +328,7 @@
       }
       methodBodyMap.put(x.getBody(), jsFunction);
       jsFunction.getSourceInfo().addCorrelation(Correlation.by(jsFunction));
+      jsFunction.getSourceInfo().addCorrelation(Correlation.by(globalName));
       push(jsFunction.getScope());
       return true;
     }
@@ -1442,10 +1451,6 @@
           JsNameRef superPrototypeRef = names.get(x.extnds).makeRef(sourceInfo);
           newExpr.setConstructorExpression(superPrototypeRef);
           JsNode<?> staticRef = superPrototypeRef.getName().getStaticRef();
-          if (staticRef != null) {
-            seedFunc.getSourceInfo().addSupertypeAncestors(
-                staticRef.getSourceInfo());
-          }
           rhs = newExpr;
         } else {
           rhs = new JsObjectLiteral(sourceInfo);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptLiterals.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptLiterals.java
index f6152e8..0e4a148 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptLiterals.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptLiterals.java
@@ -58,30 +58,32 @@
 
   @Override
   public final void endVisit(JCharLiteral x, Context ctx) {
-    push(program.getNumberLiteral(x.getValue()));
+    push(program.getNumberLiteral(x.getSourceInfo(), x.getValue()));
   }
 
   @Override
   public final void endVisit(JDoubleLiteral x, Context ctx) {
-    push(program.getNumberLiteral(x.getValue()));
+    push(program.getNumberLiteral(x.getSourceInfo(), x.getValue()));
   }
 
   @Override
   public final void endVisit(JFloatLiteral x, Context ctx) {
-    push(program.getNumberLiteral(x.getValue()));
+    push(program.getNumberLiteral(x.getSourceInfo(), x.getValue()));
   }
 
   @Override
   public final void endVisit(JIntLiteral x, Context ctx) {
-    push(program.getNumberLiteral(x.getValue()));
+    push(program.getNumberLiteral(x.getSourceInfo(), x.getValue()));
   }
 
   @Override
   public void endVisit(JLongLiteral x, Context ctx) {
     JsArrayLiteral arrayLit = new JsArrayLiteral(x.getSourceInfo());
     double[] doubleArray = LongLib.typeChange(x.getValue());
-    arrayLit.getExpressions().add(program.getNumberLiteral(doubleArray[0]));
-    arrayLit.getExpressions().add(program.getNumberLiteral(doubleArray[1]));
+    arrayLit.getExpressions().add(
+        program.getNumberLiteral(x.getSourceInfo(), doubleArray[0]));
+    arrayLit.getExpressions().add(
+        program.getNumberLiteral(x.getSourceInfo(), doubleArray[1]));
     push(arrayLit);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/js/JsParser.java b/dev/core/src/com/google/gwt/dev/js/JsParser.java
index 32a5fed..750f102 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsParser.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsParser.java
@@ -157,7 +157,7 @@
     SourceInfo parent = sourceInfoStack.peek();
     SourceInfo toReturn = program.createSourceInfo(node.getLineno()
         + parent.getStartLine() + 1, parent.getFileName());
-    toReturn.addAdditonalAncestors(parent);
+    toReturn.copyMissingCorrelationsFrom(parent);
     return toReturn;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
new file mode 100644
index 0000000..080a38e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.dev.jjs.HasSourceInfo;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.js.ast.JsVisitable;
+import com.google.gwt.dev.util.AbstractTextOutput;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A variation on the standard source generation visitor that records the
+ * locations of SourceInfo objects in the output.
+ */
+public class JsReportGenerationVisitor extends JsSourceGenerationVisitor {
+  /**
+   * A TextOutput that can return the total number of characters that have been
+   * printed.
+   */
+  public static class CountingTextOutput extends AbstractTextOutput {
+    private final StringWriter sw = new StringWriter();
+    private final PrintWriter out = new PrintWriter(sw);
+
+    public CountingTextOutput(boolean compact) {
+      super(compact);
+      setPrintWriter(out);
+    }
+
+    public int getCount() {
+      out.flush();
+      return sw.getBuffer().length();
+    }
+
+    @Override
+    public String toString() {
+      out.flush();
+      return sw.toString();
+    }
+  }
+
+  private final Map<Range, SourceInfo> sourceInfoMap = new HashMap<Range, SourceInfo>();
+  private final CountingTextOutput out;
+
+  public JsReportGenerationVisitor(CountingTextOutput out) {
+    super(out);
+    this.out = out;
+  }
+
+  public Map<Range, SourceInfo> getSourceInfoMap() {
+    return Collections.unmodifiableMap(sourceInfoMap);
+  }
+
+  @Override
+  protected <T extends JsVisitable<T>> T doAccept(T node) {
+    boolean addEntry = node instanceof HasSourceInfo;
+    int start = addEntry ? out.getCount() : 0;
+    T toReturn = super.doAccept(node);
+    if (addEntry) {
+      SourceInfo info = ((HasSourceInfo) node).getSourceInfo();
+      sourceInfoMap.put(new Range(start, out.getCount()), info);
+    }
+    return toReturn;
+  }
+
+  @Override
+  protected <T extends JsVisitable<T>> void doAcceptList(List<T> collection) {
+    for (T t : collection) {
+      doAccept(t);
+    }
+  }
+
+  @Override
+  protected <T extends JsVisitable<T>> void doAcceptWithInsertRemove(
+      List<T> collection) {
+    for (T t : collection) {
+      doAccept(t);
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/SourceInfoHistogram.java b/dev/core/src/com/google/gwt/dev/js/SourceInfoHistogram.java
deleted file mode 100644
index 8f262c9..0000000
--- a/dev/core/src/com/google/gwt/dev/js/SourceInfoHistogram.java
+++ /dev/null
@@ -1,633 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.js;
-
-import com.google.gwt.dev.jjs.Correlation;
-import com.google.gwt.dev.jjs.HasSourceInfo;
-import com.google.gwt.dev.jjs.SourceInfo;
-import com.google.gwt.dev.jjs.Correlation.Axis;
-import com.google.gwt.dev.js.ast.JsExpression;
-import com.google.gwt.dev.js.ast.JsFunction;
-import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsValueLiteral;
-import com.google.gwt.dev.js.ast.JsVisitable;
-import com.google.gwt.dev.js.ast.JsVisitor;
-import com.google.gwt.dev.util.DefaultTextOutput;
-import com.google.gwt.dev.util.HtmlTextOutput;
-import com.google.gwt.dev.util.TextOutput;
-import com.google.gwt.dev.util.Util;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-/**
- * This is a test reporting visitor for SOYC experiments. It will likely
- * disappear once a proper export format and viewer application are written.
- */
-public class SourceInfoHistogram {
-  private static class DependencyReportVisitor extends JsVisitor {
-    private final Map<Correlation, Set<Correlation>> deps = new TreeMap<Correlation, Set<Correlation>>(Correlation.AXIS_IDENT_COMPARATOR);
-    private final Stack<HasSourceInfo> currentContext = new Stack<HasSourceInfo>();
-
-    @Override
-    protected <T extends JsVisitable<T>> T doAccept(T node) {
-      /*
-       * The casts to Object here are because javac 1.5.0_16 doesn't think T
-       * could ever be coerced to JsNode.
-       */
-      boolean createScope = ((Object) node) instanceof JsProgram
-          || ((Object) node) instanceof JsFunction;
-
-      if (createScope) {
-        currentContext.push((HasSourceInfo) node);
-      }
-
-      // JsValueLiterals are shared AST nodes and distort dependency info
-      if (!(((Object) node) instanceof JsValueLiteral)
-          && !currentContext.isEmpty()) {
-        Set<Correlation> toAdd = ((HasSourceInfo) node).getSourceInfo().getAllCorrelations();
-
-        HasSourceInfo context = currentContext.peek();
-        for (Correlation c : context.getSourceInfo().getAllCorrelations()) {
-          Set<Correlation> set = deps.get(c);
-          if (set == null) {
-            deps.put(c, set = new TreeSet<Correlation>(Correlation.AXIS_IDENT_COMPARATOR));
-          }
-          set.addAll(toAdd);
-        }
-      }
-
-      T toReturn = super.doAccept(node);
-      if (createScope) {
-        currentContext.pop();
-      }
-
-      return toReturn;
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> void doAcceptList(List<T> collection) {
-      for (T node : collection) {
-        doAccept(node);
-      }
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> void doAcceptWithInsertRemove(
-        List<T> collection) {
-      for (T node : collection) {
-        doAccept(node);
-      }
-    }
-  }
-
-  private static class HSVUtils {
-    private static final String[] VALUES = {
-        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
-        "e", "f"};
-
-    /**
-     * The number of degrees to advance around H-space.
-     */
-    private static final int DEGREES = 53;
-    private static final double SATURATION = 0.2;
-    private static final double VALUE = 0.9;
-
-    /**
-     * We go around and around the HSV color space to generate colors.
-     * 
-     * @param color The index of the item to be colored.
-     * @return An RGB in hex format for the color.
-     */
-    public static String color(int color) {
-      final int h = (DEGREES * color) % 360;
-      final double s = SATURATION;
-      final double v = VALUE;
-
-      final int hi = (int) Math.floor(h / 60.) % 6;
-      final double f = (h / 60.) - hi;
-      final double p = v * (1 - s);
-      final double q = v * (1 - f * s);
-      final double t = v * (1 - (1 - f) * s);
-
-      final double r, g, b;
-      switch (hi) {
-        case 0:
-          r = v;
-          g = t;
-          b = p;
-          break;
-        case 1:
-          r = q;
-          g = v;
-          b = p;
-          break;
-        case 2:
-          r = p;
-          g = v;
-          b = t;
-          break;
-        case 3:
-          r = p;
-          g = q;
-          b = v;
-          break;
-        case 4:
-          r = t;
-          g = p;
-          b = v;
-          break;
-        case 5:
-          r = v;
-          g = p;
-          b = q;
-          break;
-        default:
-          throw new RuntimeException("Unexpected hi of " + hi);
-      }
-
-      return numberToHex(r) + numberToHex(g) + numberToHex(b);
-    }
-
-    /**
-     * Convert a number between 0 and 1 to a two-char hex value between 00 and
-     * FF.
-     */
-    private static String numberToHex(double number) {
-      number *= 255;
-      if (number <= 0) {
-        return "00";
-      } else if (number >= 255) {
-        return "FF";
-      }
-
-      String toReturn = VALUES[(int) number / 16] + VALUES[(int) number % 16];
-      return toReturn;
-    }
-
-    /**
-     * Utility class, no public constructor.
-     */
-    private HSVUtils() {
-    }
-  }
-
-  private static class JavaNormalReportVisitor extends
-      JsSourceGenerationVisitor {
-    Stack<HasSourceInfo> stack = new Stack<HasSourceInfo>();
-    int total = 0;
-    private final Map<Correlation, StringBuilder> sourceByCorrelation = new TreeMap<Correlation, StringBuilder>(Correlation.AXIS_IDENT_COMPARATOR);
-    private final Map<Correlation, StringBuilder> sourceByAllCorrelation = new TreeMap<Correlation, StringBuilder>(Correlation.AXIS_IDENT_COMPARATOR);
-    private final SwitchTextOutput out;
-
-    public JavaNormalReportVisitor(SwitchTextOutput out) {
-      super(out);
-      this.out = out;
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> T doAccept(T node) {
-      boolean openContext = node instanceof HasSourceInfo;
-      SourceInfo sourceInfo = null;
-      try {
-        if (openContext) {
-          sourceInfo = ((HasSourceInfo) node).getSourceInfo();
-          if (!stack.isEmpty()) {
-            int count = commit(stack.peek(), true);
-            accumulateTotal(sourceInfo, count);
-          }
-          stack.push((HasSourceInfo) node);
-          out.begin();
-        }
-        return super.doAccept(node);
-      } catch (RuntimeException e) {
-        e.printStackTrace();
-        throw e;
-      } finally {
-        if (openContext) {
-          int count = commit((HasSourceInfo) node, false);
-          accumulateTotal(sourceInfo, count);
-          if (stack.pop() != node) {
-            throw new RuntimeException("Unexpected node popped");
-          }
-        }
-      }
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> void doAcceptList(List<T> collection) {
-      for (T node : collection) {
-        doAccept(node);
-      }
-    }
-
-    @Override
-    protected JsExpression doAcceptLvalue(JsExpression expr) {
-      return doAccept(expr);
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> void doAcceptWithInsertRemove(
-        List<T> collection) {
-      doAcceptList(collection);
-    }
-
-    private void accumulateTotal(SourceInfo sourceInfo, int count) {
-      total += count;
-    }
-
-    private int commit(HasSourceInfo x, boolean expectMore) {
-      SourceInfo info = x.getSourceInfo();
-      List<StringBuilder> builders = new ArrayList<StringBuilder>();
-
-      // This should be an accurate count
-      for (Correlation c : info.getPrimaryCorrelations()) {
-        StringBuilder builder = sourceByCorrelation.get(c);
-        if (builder == null) {
-          builder = new StringBuilder();
-          sourceByCorrelation.put(c, builder);
-        }
-        builders.add(builder);
-      }
-
-      /*
-       * This intentionally overcounts base classes, methods in order to show
-       * aggregate based on subtypes.
-       */
-      for (Correlation c : info.getAllCorrelations()) {
-        StringBuilder builder = sourceByAllCorrelation.get(c);
-        if (builder == null) {
-          builder = new StringBuilder();
-          sourceByAllCorrelation.put(c, builder);
-        }
-        builders.add(builder);
-      }
-
-      if (expectMore) {
-        return out.flush(builders);
-      } else {
-        return out.commit(builders);
-      }
-    }
-  }
-
-  private static class JsNormalReportVisitor extends JsSourceGenerationVisitor {
-    private final HtmlTextOutput out;
-    private final Stack<SourceInfo> context = new Stack<SourceInfo>();
-
-    public JsNormalReportVisitor(HtmlTextOutput out) {
-      super(out);
-      this.out = out;
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> T doAccept(T node) {
-      boolean openNode = false;
-      if (node instanceof HasSourceInfo) {
-        SourceInfo info = ((HasSourceInfo) node).getSourceInfo();
-        openNode = context.isEmpty()
-            || SourceInfo.LOCATION_COMPARATOR.compare(context.peek(), info) != 0;
-        if (openNode) {
-          String color;
-          if (context.contains(info)) {
-            color = HSVUtils.color(context.indexOf(info));
-          } else {
-            color = HSVUtils.color(context.size());
-          }
-          context.push(info);
-          out.printRaw("<div class=\"node\" style=\"background:#" + color
-              + ";\">");
-          out.printRaw("<div class=\"story\">");
-          out.print(info.getStory());
-          out.printRaw("</div>");
-        }
-      }
-      T toReturn = super.doAccept(node);
-      if (openNode) {
-        out.printRaw("</div>");
-        context.pop();
-      }
-      return toReturn;
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> void doAcceptList(List<T> collection) {
-      for (T node : collection) {
-        doAccept(node);
-      }
-    }
-
-    @Override
-    protected JsExpression doAcceptLvalue(JsExpression expr) {
-      return doAccept(expr);
-    }
-
-    @Override
-    protected <T extends JsVisitable<T>> void doAcceptWithInsertRemove(
-        List<T> collection) {
-      doAcceptList(collection);
-    }
-  }
-
-  private static class SwitchTextOutput implements TextOutput {
-    private Stack<DefaultTextOutput> outs = new Stack<DefaultTextOutput>();
-
-    public void begin() {
-      outs.push(new DefaultTextOutput(true));
-    }
-
-    public int commit(Collection<StringBuilder> builders) {
-      String string = outs.pop().toString();
-      for (StringBuilder builder : builders) {
-        builder.append(string);
-      }
-      return string.length();
-    }
-
-    public int flush(Collection<StringBuilder> builders) {
-      int toReturn = commit(builders);
-      begin();
-      return toReturn;
-    }
-
-    public void indentIn() {
-      // outs.peek().indentIn();
-    }
-
-    public void indentOut() {
-      // outs.peek().indentOut();
-    }
-
-    public void newline() {
-      outs.peek().newline();
-    }
-
-    public void newlineOpt() {
-      outs.peek().newlineOpt();
-    }
-
-    public void print(char c) {
-      outs.peek().print(c);
-    }
-
-    public void print(char[] s) {
-      outs.peek().print(s);
-    }
-
-    public void print(String s) {
-      outs.peek().print(s);
-    }
-
-    public void printOpt(char c) {
-      outs.peek().printOpt(c);
-    }
-
-    public void printOpt(char[] s) {
-      outs.peek().printOpt(s);
-    }
-
-    public void printOpt(String s) {
-      outs.peek().printOpt(s);
-    }
-  }
-
-  public static void exec(JsProgram program, String outputPath) {
-    writeDependencyReport(program, outputPath);
-    writeJavaNormalReport(program, outputPath);
-    writeJsNormalReport(program, outputPath);
-  }
-
-  private static void writeDependencyReport(JsProgram program, String outputPath) {
-    DependencyReportVisitor v = new DependencyReportVisitor();
-    v.accept(program);
-
-    Map<Correlation, Integer> idents = new HashMap<Correlation, Integer>();
-    TreeMap<String, Set<Integer>> clusters = new TreeMap<String, Set<Integer>>();
-    for (Correlation c : v.deps.keySet()) {
-      if (c.getAxis().equals(Axis.CLASS)) {
-        clusters.put(c.getIdent(), new TreeSet<Integer>());
-      }
-    }
-
-    EnumSet<Axis> toShow = EnumSet.of(Axis.METHOD, Axis.FIELD);
-
-    StringBuilder edges = new StringBuilder();
-    StringBuilder nodes = new StringBuilder();
-    StringBuilder subgraphs = new StringBuilder();
-
-    for (Map.Entry<Correlation, Set<Correlation>> entry : v.deps.entrySet()) {
-      Correlation key = entry.getKey();
-
-      if (!toShow.contains(key.getAxis())
-          || key.getIdent().startsWith("java.lang")) {
-        continue;
-      }
-
-      Set<Integer> keyClusterSet;
-      if (!idents.containsKey(key)) {
-        idents.put(key, idents.size());
-        nodes.append(idents.get(key) + " [label=\"" + key + "\"];\n");
-      }
-      if (key.getAxis().isJava()) {
-        keyClusterSet = clusters.get(clusters.headMap(key.getIdent()).lastKey());
-        keyClusterSet.add(idents.get(key));
-      } else {
-        keyClusterSet = null;
-      }
-
-      for (Correlation c : entry.getValue()) {
-        if (!toShow.contains(c.getAxis())
-            || c.getIdent().startsWith("java.lang")) {
-          continue;
-        }
-
-        Set<Integer> cClusterSet;
-        if (!idents.containsKey(c)) {
-          idents.put(c, idents.size());
-          nodes.append(idents.get(c) + " [label=\"" + c + "\"];\n");
-        }
-        if (c.getAxis().isJava()) {
-          cClusterSet = clusters.get(clusters.headMap(c.getIdent()).lastKey());
-          cClusterSet.add(idents.get(c));
-        } else {
-          cClusterSet = null;
-        }
-
-        edges.append(idents.get(key) + " -> " + idents.get(c));
-        if (keyClusterSet == cClusterSet) {
-          edges.append(" constraint=false");
-        }
-        edges.append(";\n");
-      }
-    }
-    int clusterNumber = 0;
-    for (Map.Entry<String, Set<Integer>> entry : clusters.entrySet()) {
-      Set<Integer> set = entry.getValue();
-      if (set.isEmpty()) {
-        continue;
-      }
-
-      subgraphs.append("subgraph cluster" + clusterNumber++ + " {");
-      subgraphs.append("label=\"" + entry.getKey() + "\";");
-      for (Integer i : set) {
-        subgraphs.append(i + "; ");
-      }
-      subgraphs.append("};\n");
-    }
-
-    PrintWriter out;
-    try {
-      File outputPathDir = new File(outputPath);
-      outputPathDir.mkdirs();
-      out = new PrintWriter(new FileWriter(File.createTempFile("soyc",
-          "-deps.dot", outputPathDir)));
-    } catch (IOException e) {
-      out = null;
-    }
-
-    out.println("digraph soyc {");
-    out.println(subgraphs.toString());
-    out.println(nodes.toString());
-    out.println(edges.toString());
-    out.println("}");
-    out.close();
-  }
-
-  private static void writeJavaNormalReport(JsProgram program, String outputPath) {
-    JavaNormalReportVisitor v = new JavaNormalReportVisitor(
-        new SwitchTextOutput());
-    v.accept(program);
-
-    PrintWriter out;
-    try {
-      File outputPathDir = new File(outputPath);
-      outputPathDir.mkdirs();
-      out = new PrintWriter(new FileWriter(File.createTempFile("soyc",
-          "-java.html", outputPathDir)));
-    } catch (IOException e) {
-      out = null;
-    }
-
-    out.println("<html><head>");
-    out.println("<style>"
-        + "* {font-family: monospace;}"
-        + ".file {clear: both;}"
-        + ".file * {display: none;}"
-        + ".file .fileHeader, .file .fileHeader * {display: block; cursor: pointer;}"
-        + ".fileOpen .fileHeader {clear: both;}"
-        + ".fileOpen .javaLine {clear: both; float: left; white-space: pre; background: #efe;}"
-        + ".fileOpen .jsLine {outline: thin solid black; float: right; clear: right; white-space: pre; background: #ddd;}"
-        + ".story {display:none;}"
-        + "div.jsLine:hover .story{display:block; position: absolute; left:0; background: #eef;}"
-        + "</style>");
-
-    out.println("</head><body>");
-
-    out.println(String.format("<h1>Total bytes: %d</h1>", v.total));
-    Map<Axis, Integer> totalsByAxis = new EnumMap<Axis, Integer>(Axis.class);
-    for (Map.Entry<Correlation, StringBuilder> entry : v.sourceByCorrelation.entrySet()) {
-      Correlation c = entry.getKey();
-      StringBuilder builder = entry.getValue();
-      int count = builder.length();
-      out.println("<div class=\"file\" onclick=\"this.className=(this.className=='file'?'fileOpen':'file')\">");
-      out.println("<div class=\"fileHeader\">" + Util.escapeXml(c.toString())
-          + " : " + count + "</div>");
-      out.print("<div class=\"jsLine\">");
-      out.print(Util.escapeXml(builder.toString()));
-      out.print("</div></div>");
-
-      Axis axis = c.getAxis();
-      Integer t = totalsByAxis.get(axis);
-      if (t == null) {
-        totalsByAxis.put(axis, count);
-      } else {
-        totalsByAxis.put(axis, t + count);
-      }
-    }
-
-    out.println("<h1>Axis totals</h1>");
-    for (Map.Entry<Axis, Integer> entry : totalsByAxis.entrySet()) {
-      out.println("<div>" + entry.getKey() + " : " + entry.getValue()
-          + "</div>");
-    }
-
-    out.println("<h1>Cost of polymorphism</h1>");
-    for (Map.Entry<Correlation, StringBuilder> entry : v.sourceByAllCorrelation.entrySet()) {
-      Correlation c = entry.getKey();
-      StringBuilder builder = entry.getValue();
-      int count = builder.length();
-
-      StringBuilder uniqueOutput = v.sourceByCorrelation.get(c);
-      int uniqueCount = uniqueOutput == null ? 0 : uniqueOutput.length();
-      boolean bold = count != uniqueCount;
-
-      out.println("<div class=\"file\" onclick=\"this.className=(this.className=='file'?'fileOpen':'file')\">");
-      out.println("<div class=\"fileHeader\">" + (bold ? "<b>" : "")
-          + Util.escapeXml(c.toString()) + " : " + count + " versus "
-          + uniqueCount + "(" + (count - uniqueCount) + ")"
-          + (bold ? "</b>" : "") + "</div>");
-      out.print("<div class=\"jsLine\">");
-      out.print(Util.escapeXml(builder.toString()));
-      out.print("</div></div>");
-    }
-
-    out.println("<h1>Done</h1>");
-    out.println("</body></html>");
-    out.close();
-  }
-
-  private static void writeJsNormalReport(JsProgram program, String outputPath) {
-
-    PrintWriter out;
-    try {
-      File outputPathDir = new File(outputPath);
-      outputPathDir.mkdirs();
-      out = new PrintWriter(new FileWriter(File.createTempFile("soyc",
-          "-js.html", outputPathDir)));
-    } catch (IOException e) {
-      out = null;
-    }
-
-    out.println("<html><head>");
-    out.println("<style>" + "* {white-space: pre; font-family: monospace;}"
-        + ".node {display:inline; z-index: 0;}" + ".story {display: none;}"
-        + "div.node:hover > .story {"
-        + "  display:block; float:right; clear: right; background: inherit; "
-        + "  position: relative; border-left: 8px solid white; z-index: 1; max-width:50%;}"
-        + "</style>");
-    out.println("</head><body>");
-
-    HtmlTextOutput htmlOut = new HtmlTextOutput(out, false);
-    JsNormalReportVisitor v = new JsNormalReportVisitor(htmlOut);
-    v.accept(program);
-
-    out.println("</body></html>");
-    out.close();
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
index 1792dbe..03c3433 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
@@ -136,7 +136,7 @@
     if (!hasStaticRef && name != null) {
       JsNode<?> staticRef = name.getStaticRef();
       if (staticRef != null) {
-        toReturn.addAdditonalAncestors(name.getStaticRef().getSourceInfo());
+        toReturn.copyMissingCorrelationsFrom(name.getStaticRef().getSourceInfo());
         hasStaticRef = true;
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
index e6b6b9e..33dd57f 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
@@ -15,7 +15,10 @@
  */
 package com.google.gwt.dev.js.ast;
 
+import com.google.gwt.dev.jjs.Correlation;
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.Correlation.Axis;
+import com.google.gwt.dev.jjs.Correlation.Literal;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -24,22 +27,30 @@
  * A JavaScript program.
  */
 public final class JsProgram extends JsNode<JsProgram> {
+  /**
+   * This method is used to create SourceInfos for fields in JsProgram. This
+   * method always creates a SourceInfo that has collection enabled.
+   */
+  private static SourceInfo createSourceInfoEnabled(String description) {
+    return new SourceInfoJs(-1, -1, 0, JsProgram.class.getName(), true).makeChild(
+        JsProgram.class, description);
+  }
 
   private final JsStatement debuggerStmt = new JsDebugger(
-      createSourceInfoSynthetic(JsProgram.class, "debugger statement"));
+      createSourceInfoEnabled("debugger statement"));
 
-  private final JsEmpty emptyStmt = new JsEmpty(createSourceInfoSynthetic(
-      JsProgram.class, "Empty statement"));
+  private final JsEmpty emptyStmt = new JsEmpty(
+      createSourceInfoEnabled("Empty statement"));
 
-  private boolean enableSourceInfoDescendants;
+  private final boolean enableSourceInfoDescendants;
 
   private final JsBooleanLiteral falseLiteral = new JsBooleanLiteral(
-      createSourceInfoSynthetic(JsProgram.class, "false literal"), false);
+      createSourceInfoEnabled("false literal"), false);
 
   private JsProgramFragment[] fragments;
 
   private final JsNullLiteral nullLiteral = new JsNullLiteral(
-      createSourceInfoSynthetic(JsProgram.class, "null literal"));
+      createSourceInfoEnabled("null literal"));
 
   private final Map<Double, JsNumberLiteral> numberLiteralMap = new HashMap<Double, JsNumberLiteral>();
 
@@ -49,21 +60,42 @@
 
   private final Map<String, JsStringLiteral> stringLiteralMap = new HashMap<String, JsStringLiteral>();
 
+  private final SourceInfo stringPoolSourceInfo;
+
   private final JsScope topScope;
 
   private final JsBooleanLiteral trueLiteral = new JsBooleanLiteral(
-      createSourceInfoSynthetic(JsProgram.class, "true literal"), true);
+      createSourceInfoEnabled("true literal"), true);
+
+  public JsProgram() {
+    this(false);
+  }
 
   /**
    * Constructs a JavaScript program object.
+   * 
+   * @param soycEnabled Controls whether or not SourceInfo nodes created via the
+   *          JsProgram will record descendant information. Enabling this
+   *          feature will collect extra data during the compilation cycle, but
+   *          at a cost of memory and object allocations.
    */
-  public JsProgram() {
+  public JsProgram(boolean soycEnabled) {
     super(SourceInfoJs.INTRINSIC.makeChild(JsProgram.class,
         "JavaScript program"));
+    this.enableSourceInfoDescendants = soycEnabled;
+
     rootScope = new JsRootScope(this);
     topScope = new JsScope(rootScope, "Global");
     objectScope = new JsScope(rootScope, "Object");
     setFragmentCount(1);
+    falseLiteral.getSourceInfo().addCorrelation(
+        Correlation.by(Literal.JS_BOOLEAN));
+    nullLiteral.getSourceInfo().addCorrelation(Correlation.by(Literal.JS_NULL));
+    trueLiteral.getSourceInfo().addCorrelation(
+        Correlation.by(Literal.JS_BOOLEAN));
+    stringPoolSourceInfo = createSourceInfoSynthetic(JsProgram.class,
+        "String pool");
+    stringPoolSourceInfo.addCorrelation(Correlation.by(Literal.JS_STRING));
   }
 
   public SourceInfo createSourceInfo(int lineNumber, String location) {
@@ -124,13 +156,35 @@
   }
 
   public JsNumberLiteral getNumberLiteral(double value) {
-    JsNumberLiteral lit = numberLiteralMap.get(value);
-    if (lit == null) {
-      lit = new JsNumberLiteral(createSourceInfoSynthetic(JsProgram.class,
-          "Number literal " + value), value);
-      numberLiteralMap.put(value, lit);
+    return getNumberLiteral(null, value);
+  }
+
+  public JsNumberLiteral getNumberLiteral(SourceInfo info, double value) {
+    /*
+     * This method only canonicalizes number literals when we don't have an
+     * incoming SourceInfo so that we can distinguish int-0 from double-0 in the
+     * analysis.
+     */
+    if (info == null) {
+      JsNumberLiteral lit = numberLiteralMap.get(value);
+      if (lit == null) {
+        info = createSourceInfoSynthetic(JsProgram.class, "Number literal "
+            + value);
+        info.addCorrelation(Correlation.by(Literal.JS_NUMBER));
+        lit = new JsNumberLiteral(info, value);
+        numberLiteralMap.put(value, lit);
+      }
+
+      return lit;
+    } else {
+      // Only add a JS_NUMBER if no literal correlation present: e.g. Java int
+      if (info.getPrimaryCorrelation(Axis.LITERAL) == null) {
+        // Don't mutate incoming SourceInfo
+        info = info.makeChild(JsProgram.class, "Number literal " + value);
+        info.addCorrelation(Correlation.by(Literal.JS_NUMBER));
+      }
+      return new JsNumberLiteral(info, value);
     }
-    return lit;
   }
 
   public JsScope getObjectScope() {
@@ -162,11 +216,11 @@
   public JsStringLiteral getStringLiteral(SourceInfo sourceInfo, String value) {
     JsStringLiteral lit = stringLiteralMap.get(value);
     if (lit == null) {
-      lit = new JsStringLiteral(sourceInfo, value);
+      lit = new JsStringLiteral(stringPoolSourceInfo.makeChild(JsProgram.class,
+          "String literal: " + value), value);
       stringLiteralMap.put(value, lit);
-    } else {
-      lit.getSourceInfo().addAdditonalAncestors(sourceInfo);
     }
+    lit.getSourceInfo().merge(sourceInfo);
     return lit;
   }
 
@@ -175,18 +229,10 @@
   }
 
   public JsNameRef getUndefinedLiteral() {
-    return rootScope.findExistingName("undefined").makeRef(
-        createSourceInfoSynthetic(JsProgram.class, "undefined reference"));
-  }
-
-  /**
-   * Controls whether or not SourceInfo nodes created via the JsProgram will
-   * record descendant information. Enabling this feature will collect extra
-   * data during the compilation cycle, but at a cost of memory and object
-   * allocations.
-   */
-  public void setEnableSourceInfoDescendants(boolean enable) {
-    enableSourceInfoDescendants = enable;
+    SourceInfo info = createSourceInfoSynthetic(JsProgram.class,
+        "undefined reference");
+    info.addCorrelation(Correlation.by(Literal.JS_UNDEFINED));
+    return rootScope.findExistingName("undefined").makeRef(info);
   }
 
   public void setFragmentCount(int fragments) {
diff --git a/dev/core/src/com/google/gwt/dev/util/FileBackedObject.java b/dev/core/src/com/google/gwt/dev/util/FileBackedObject.java
new file mode 100644
index 0000000..af62425
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/FileBackedObject.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Represents a File that contains the serialized form of a Serializable object.
+ * 
+ * @param <T> the type of object serialized into the file
+ */
+public class FileBackedObject<T extends Serializable> implements Serializable {
+  private final File backingFile;
+  private final Class<T> clazz;
+
+  /**
+   * Constructs an empty FileBackedObject. A temporary File will be used and
+   * this file will be deleted when the JVM exits.
+   * 
+   * @param clazz the type of object to be serialized
+   * @throws IOException if the temporary file could not be created
+   */
+  public FileBackedObject(Class<T> clazz) throws IOException {
+    this(clazz, File.createTempFile("fileBackedObject", ".ser"));
+    backingFile.deleteOnExit();
+  }
+
+  /**
+   * Constructs a FileBackedObject using an existing File object.
+   * 
+   * @param clazz the type of object to be serialized
+   * @param backingFile the file to read from or write to
+   */
+  public FileBackedObject(Class<T> clazz, File backingFile) {
+    this.clazz = clazz;
+    this.backingFile = backingFile;
+  }
+
+  /**
+   * Returns the underlying File object.
+   */
+  public File getFile() {
+    return backingFile;
+  }
+
+  /**
+   * Construct a new instance of the object stored in the backing file.
+   * 
+   * @param logger a sink for error messages
+   * @return a new instance of the object stored in the backing file
+   * @throws UnableToCompleteException if the backing store does not contain an
+   *           object of type <code>T</code>
+   */
+  public T newInstance(TreeLogger logger) throws UnableToCompleteException {
+    try {
+      T toReturn = Util.readFileAsObject(backingFile, clazz);
+      if (toReturn == null) {
+        logger.log(TreeLogger.ERROR, "Unable to instantiate object");
+        throw new UnableToCompleteException();
+      }
+      return toReturn;
+    } catch (ClassNotFoundException e) {
+      logger.log(TreeLogger.ERROR, "Missing class definition", e);
+      throw new UnableToCompleteException();
+    }
+  }
+
+  /**
+   * Set the contents of the backing file.
+   * 
+   * @param logger a sink for error messages
+   * @param object the object to store
+   * @throws UnableToCompleteException if the object could not be serialized
+   */
+  public void set(TreeLogger logger, T object) throws IllegalStateException,
+      UnableToCompleteException {
+    assert clazz.isInstance(object);
+    Util.writeObjectAsFile(logger, backingFile, object);
+  }
+
+  @Override
+  public String toString() {
+    return backingFile.toString() + "<" + clazz.getName() + ">";
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerSoyc.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerSoyc.java
new file mode 100644
index 0000000..0bcdabd
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerSoyc.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerFlag;
+
+/**
+ * An ArgHandler that enables Story Of Your Compile data-collection.
+ */
+public class ArgHandlerSoyc extends ArgHandlerFlag {
+
+  private final OptionSoycEnabled options;
+
+  public ArgHandlerSoyc(OptionSoycEnabled options) {
+    this.options = options;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "Enable Story Of Your Compile";
+  }
+
+  @Override
+  public String getTag() {
+    return "-soyc";
+  }
+
+  @Override
+  public boolean setFlag() {
+    options.setSoycEnabled(true);
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionSoycEnabled.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionSoycEnabled.java
new file mode 100644
index 0000000..3e81c37
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionSoycEnabled.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Option for enabling Story Of Your Compile data-collection.
+ */
+public interface OptionSoycEnabled {
+  boolean isSoycEnabled();
+
+  void setSoycEnabled(boolean enabled);
+}
diff --git a/doc/build.xml b/doc/build.xml
index 6e81eec..6eed4cb 100644
--- a/doc/build.xml
+++ b/doc/build.xml
@@ -10,7 +10,7 @@
   <property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev-linux.jar" />
 
   <property name="USER_PKGS"
-          value="com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.linker;com.google.gwt.core.ext.typeinfo;com.google.gwt.dom.client;com.google.gwt.i18n.client;com.google.gwt.i18n.rebind.format;com.google.gwt.i18n.rebind.keygen;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.benchmarks.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.server.rpc;com.google.gwt.xml.client;com.google.gwt.http.client;com.google.gwt.animation.client" />
+          value="com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.soyc;com.google.gwt.core.ext.linker;com.google.gwt.core.ext.typeinfo;com.google.gwt.dom.client;com.google.gwt.i18n.client;com.google.gwt.i18n.rebind.format;com.google.gwt.i18n.rebind.keygen;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.benchmarks.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.server.rpc;com.google.gwt.xml.client;com.google.gwt.http.client;com.google.gwt.animation.client" />
   <property name="LANG_PKGS" value="java.lang;java.lang.annotation;java.util;java.io;java.sql" />
 
   <!--	Individual classes to include when we don't want to 
diff --git a/user/src/com/google/gwt/core/Core.gwt.xml b/user/src/com/google/gwt/core/Core.gwt.xml
index fa52b58..7448eca 100644
--- a/user/src/com/google/gwt/core/Core.gwt.xml
+++ b/user/src/com/google/gwt/core/Core.gwt.xml
@@ -25,4 +25,7 @@
   <define-linker name="sso" class="com.google.gwt.core.linker.SingleScriptLinker" />
   
   <add-linker name="std" />
+  
+  <define-linker class="com.google.gwt.core.linker.soyc.SoycReportLinker" name="soycReport" />
+  <add-linker name="soycReport" />
 </module>