Remove Correlations & SourceInfo memory overhead from non-SOYC builds.

Review by: bobv

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5168 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
index 3cc1cd9..60cdb8c 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
@@ -16,7 +16,7 @@
 package com.google.gwt.dev.jdt;
 
 import com.google.gwt.dev.jjs.InternalCompilerException;
-import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.js.JsParser;
 import com.google.gwt.dev.js.JsParserException;
 import com.google.gwt.dev.js.ast.JsContext;
@@ -127,7 +127,7 @@
     StringReader sr = new StringReader(syntheticFnHeader + '\n' + jsniCode);
     try {
       // start at -1 to avoid counting our synthetic header
-      List<JsStatement> result = JsParser.parse(SourceInfo.UNKNOWN,
+      List<JsStatement> result = JsParser.parse(SourceOrigin.UNKNOWN,
           jsProgram.getScope(), sr);
       new JsVisitor() {
         public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
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 0688003..c527440 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/Correlation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/Correlation.java
@@ -18,17 +18,11 @@
 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.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
@@ -176,113 +170,6 @@
   };
 
   /**
-   * 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) {
-    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("(");
-    for (JType type : method.getOriginalParamTypes()) {
-      sb.append(type.getJsniSignatureName());
-    }
-    sb.append(")");
-    return sb.toString();
-  }
-
-  /**
    * This may contain a reference to either a Java or Js AST node.
    */
   protected final Serializable astReference;
@@ -297,7 +184,7 @@
    */
   protected final String ident;
 
-  private Correlation(Axis axis, String ident, Serializable astReference) {
+  Correlation(Axis axis, String ident, Serializable astReference) {
     if (axis == null) {
       throw new NullPointerException("axis");
     } else if (ident == null) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java b/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java
new file mode 100644
index 0000000..038bf50
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs;
+
+import com.google.gwt.dev.jjs.Correlation.Axis;
+import com.google.gwt.dev.jjs.Correlation.Literal;
+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.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.EnumMap;
+import java.util.Map;
+
+/**
+ * A {@link Correlation} factory.
+ */
+public abstract class CorrelationFactory implements Serializable {
+
+  /**
+   * A dummy factory that always returns <code>null</code>.
+   */
+  public static final class DummyCorrelationFactory extends CorrelationFactory {
+    public Correlation by(JField field) {
+      return null;
+    }
+
+    public Correlation by(JMethod method) {
+      return null;
+    }
+
+    public Correlation by(JReferenceType type) {
+      return null;
+    }
+
+    public Correlation by(JsFunction function) {
+      return null;
+    }
+
+    public Correlation by(JsName name) {
+      return null;
+    }
+
+    public Correlation by(JsName name, boolean isAlias) {
+      return null;
+    }
+
+    public Correlation by(Literal type) {
+      return null;
+    }
+
+    public Correlation by(SourceOrigin origin) {
+      return null;
+    }
+
+    @Override
+    public SourceInfo makeSourceInfo(SourceOrigin origin) {
+      return origin;
+    }
+  }
+
+  /**
+   * A real factory that returns new {@link Correlation Correlations}.
+   */
+  public static final class RealCorrelationFactory extends CorrelationFactory {
+    /*
+     * NB: The Correlation type uses AST nodes in its factory methods to make it
+     * easier to extract whatever information we want to include in the SOYC
+     * reports without having to update call sites with additional parameters.
+     * 
+     * In the general case, references to AST nodes should not be exposed to any
+     * public-API consumers of the Correlation.
+     */
+
+    /**
+     * 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));
+      }
+    }
+
+    private static String getMethodIdent(JMethod method) {
+      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(")");
+      return sb.toString();
+    }
+
+    /**
+     * This cuts down on the total number of Correlation objects allocated.
+     */
+    @SuppressWarnings("unchecked")
+    private final Map<Object, Correlation> canonicalMap = Collections.synchronizedMap(new ReferenceMap(
+        ReferenceMap.WEAK, ReferenceMap.WEAK));
+
+    public Correlation by(JField field) {
+      Correlation toReturn = canonicalMap.get(field);
+      if (toReturn == null) {
+        toReturn = new Correlation(Axis.FIELD,
+            field.getEnclosingType().getName() + "::" + field.getName(), field);
+        canonicalMap.put(field, toReturn);
+      }
+      return toReturn;
+    }
+
+    public Correlation by(JMethod method) {
+      Correlation toReturn = canonicalMap.get(method);
+      if (toReturn == null) {
+
+        toReturn = new Correlation(Axis.METHOD, getMethodIdent(method), method);
+        canonicalMap.put(method, toReturn);
+      }
+      return toReturn;
+    }
+
+    public Correlation by(JReferenceType type) {
+      Correlation toReturn = canonicalMap.get(type);
+      if (toReturn == null) {
+        toReturn = new Correlation(Axis.CLASS, type.getName(), type);
+        canonicalMap.put(type, toReturn);
+      }
+      return toReturn;
+    }
+
+    public Correlation by(JsFunction function) {
+      Correlation toReturn = canonicalMap.get(function);
+      if (toReturn == null) {
+        toReturn = new Correlation(Axis.FUNCTION,
+            function.getName().getIdent(), function);
+        canonicalMap.put(function, toReturn);
+      }
+      return toReturn;
+    }
+
+    /**
+     * Creates a JS_NAME Correlation.
+     */
+    public 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 Correlation by(JsName name, boolean isAlias) {
+      Correlation toReturn = canonicalMap.get(name);
+      if (toReturn == null) {
+        toReturn = new Correlation(isAlias ? Axis.JS_ALIAS : Axis.JS_NAME,
+            name.getIdent(), name);
+        canonicalMap.put(name, toReturn);
+      }
+      return toReturn;
+    }
+
+    public Correlation by(Literal type) {
+      assert LITERAL_CORRELATIONS.containsKey(type);
+      return LITERAL_CORRELATIONS.get(type);
+    }
+
+    public Correlation by(SourceOrigin origin) {
+      Correlation toReturn = canonicalMap.get(origin);
+      if (toReturn == null) {
+        toReturn = new Correlation(Axis.ORIGIN, origin.getFileName() + ":"
+            + origin.getStartLine(), origin);
+        canonicalMap.put(origin, toReturn);
+      }
+      return toReturn;
+    }
+
+    @Override
+    public SourceInfo makeSourceInfo(SourceOrigin origin) {
+      return new SourceInfoCorrelation(origin);
+    }
+  }
+
+  public abstract Correlation by(JField field);
+
+  public abstract Correlation by(JMethod method);
+
+  public abstract Correlation by(JReferenceType type);
+
+  public abstract Correlation by(JsFunction function);
+
+  public abstract Correlation by(JsName name);
+
+  public abstract Correlation by(JsName name, boolean isAlias);
+
+  public abstract Correlation by(Literal type);
+
+  public abstract Correlation by(SourceOrigin origin);
+
+  public abstract SourceInfo makeSourceInfo(SourceOrigin origin);
+}
\ No newline at end of file
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 61c9682..06961e3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -30,6 +30,8 @@
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.jdt.RebindPermutationOracle;
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
+import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
+import com.google.gwt.dev.jjs.CorrelationFactory.RealCorrelationFactory;
 import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
 import com.google.gwt.dev.jjs.UnifiedAst.AST;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
@@ -375,8 +377,10 @@
     checkForErrors(logger, goldenCuds, false);
 
     PerfLogger.start("Build AST");
-    JProgram jprogram = new JProgram(options.isSoycEnabled());
-    JsProgram jsProgram = new JsProgram(options.isSoycEnabled());
+    CorrelationFactory correlator = options.isSoycEnabled()
+        ? new RealCorrelationFactory() : new DummyCorrelationFactory();
+    JProgram jprogram = new JProgram(correlator);
+    JsProgram jsProgram = new JsProgram(correlator);
 
     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 cdbfef5..baaf810 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
@@ -18,220 +18,56 @@
 import com.google.gwt.dev.jjs.Correlation.Axis;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
 /**
  * Tracks file and line information for AST nodes.
  */
-public class SourceInfo implements Serializable {
-
-  /**
-   * A totally-immutable version of SourceInfo.
-   */
-  protected static class Immutable extends SourceInfo {
-    public Immutable(int startPos, int endPos, int startLine, String fileName,
-        boolean createDescendants) {
-      super(startPos, endPos, startLine, fileName, createDescendants);
-    }
-
-    @Override
-    public void addCorrelation(Correlation c) {
-      throw new UnsupportedOperationException(
-          "May not add correlations to the " + getFileName()
-              + "SourceInfo. Call makeChild() first.");
-    }
-
-    @Override
-    public void copyMissingCorrelationsFrom(SourceInfo other) {
-      throw new UnsupportedOperationException(
-          "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.");
-      }
-    }
-  }
-
-  /**
-   * 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);
-
-  /**
-   * 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 List<Correlation> allCorrelations;
-
-  /**
-   * 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 accumulateData) {
-    assert fileName != null;
-
-    this.accumulateData = accumulateData;
-    origin = SourceOrigin.create(startPos, endPos, startLine, fileName);
-
-    // Be very aggressive in not allocating collections that we don't need.
-    if (accumulateData) {
-      allCorrelations = new ArrayList<Correlation>();
-      primaryCorrelations = new EnumMap<Axis, Correlation>(Axis.class);
-    } else {
-      allCorrelations = null;
-      primaryCorrelations = null;
-    }
-  }
-
-  private SourceInfo(SourceInfo parent, String mutation, String caller,
-      SourceInfo... additionalAncestors) {
-    assert parent != null;
-    assert caller != null;
-    this.accumulateData = parent.accumulateData;
-    this.origin = parent.origin;
-    if (accumulateData) {
-      this.allCorrelations = new ArrayList<Correlation>(parent.allCorrelations);
-      this.primaryCorrelations = new EnumMap<Axis, Correlation>(
-          parent.primaryCorrelations);
-    } else {
-      allCorrelations = null;
-      primaryCorrelations = null;
-    }
-    merge(additionalAncestors);
-  }
+public interface SourceInfo extends Serializable {
 
   /**
    * Add a Correlation to the SourceInfo.
    */
-  public void addCorrelation(Correlation c) {
-    if (!accumulateData) {
-      return;
-    }
-    if (!isAlreadyInAllCorrelations(c)) {
-      allCorrelations.add(c);
-    }
-    if (!primaryCorrelations.containsKey(c.getAxis())) {
-      primaryCorrelations.put(c.getAxis(), c);
-    }
-  }
+  void addCorrelation(Correlation c);
 
   /**
    * Copy any Correlations from another SourceInfo node if there are no
    * Correlations on this SourceInfo with the same Axis.
    */
-  public void copyMissingCorrelationsFrom(SourceInfo other) {
-    if (!accumulateData) {
-      return;
-    }
-
-    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);
-      }
-    }
-  }
+  void copyMissingCorrelationsFrom(SourceInfo other);
 
   /**
    * Returns all Correlations applied to this SourceInfo, its parent, additional
    * ancestor SourceInfo, and any supertype SourceInfos.
    */
-  public List<Correlation> getAllCorrelations() {
-    if (accumulateData) {
-      return allCorrelations;
-    } else {
-      return Collections.<Correlation> emptyList();
-    }
-  }
+  List<Correlation> getAllCorrelations();
 
   /**
    * Returns all Correlations along a given axis applied to this SourceInfo, its
    * parent, additional ancestor SourceInfo, and any supertype SourceInfos.
    */
-  public List<Correlation> getAllCorrelations(Axis axis) {
-    if (!accumulateData) {
-      return Collections.emptyList();
-    }
-    List<Correlation> toReturn = new ArrayList<Correlation>();
-    for (Correlation c : getAllCorrelations()) {
-      if (c.getAxis() == axis) {
-        toReturn.add(c);
-      }
-    }
-    return toReturn;
-  }
+  List<Correlation> getAllCorrelations(Axis axis);
 
-  public int getEndPos() {
-    return getOrigin().getEndPos();
-  }
+  int getEndPos();
 
-  public String getFileName() {
-    return getOrigin().getFileName();
-  }
-
-  public SourceOrigin getOrigin() {
-    return origin;
-  }
+  String getFileName();
 
   /**
    * 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;
-  }
+  Correlation getPrimaryCorrelation(Axis axis);
 
   /**
    * 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();
-  }
+  Set<Correlation> getPrimaryCorrelations();
 
-  public int getStartLine() {
-    return getOrigin().getStartLine();
-  }
+  int getStartLine();
 
-  public int getStartPos() {
-    return getOrigin().getStartPos();
-  }
+  int getStartPos();
 
   /**
    * If data accumulation is enabled, create a derived SourceInfo object that
@@ -239,10 +75,7 @@
    * 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 accumulateData ? makeChild(caller, description,
-        EMPTY_SOURCEINFO_ARRAY) : this;
-  }
+  SourceInfo makeChild(Class<?> caller, String description);
 
   /**
    * If data accumulation is enabled, create a derived SourceInfo object that
@@ -250,60 +83,12 @@
    * 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... merge) {
-    if (!accumulateData) {
-      return this;
-    }
-
-    String callerName = caller == null ? "Unrecorded caller" : caller.getName();
-    return new SourceInfo(this, description, callerName, merge);
-  }
+  SourceInfo makeChild(Class<?> caller, String description, SourceInfo... merge);
 
   /**
    * 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 merge(SourceInfo... sourceInfos) {
-    if (!accumulateData) {
-      return;
-    }
-
-    for (SourceInfo info : sourceInfos) {
-      if (this == info || !info.accumulateData) {
-        continue;
-      }
-
-      for (Correlation c : info.getAllCorrelations()) {
-        if (!isAlreadyInAllCorrelations(c)) {
-          allCorrelations.add(c);
-        }
-      }
-
-      if (primaryCorrelations.size() < Axis.values().length) {
-        EnumMap<Axis, Correlation> copy = new EnumMap<Axis, Correlation>(
-            info.primaryCorrelations);
-        copy.keySet().removeAll(primaryCorrelations.keySet());
-        primaryCorrelations.putAll(copy);
-      }
-    }
-  }
-
-  @Override
-  public String toString() {
-    return origin.toString();
-  }
-
-  private boolean isAlreadyInAllCorrelations(Correlation c) {
-    // make sure this correlations is not yet in the allCorrelations list
-    boolean alreadyThere = false;
-    Iterator<Correlation> it = allCorrelations.iterator();
-    while ((alreadyThere == false) && (it.hasNext())) {
-      if (it.next().equals(c)) {
-        alreadyThere = true;
-      }
-    }
-    return alreadyThere;
-  }
+  void merge(SourceInfo... sourceInfos);
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceInfoCorrelation.java b/dev/core/src/com/google/gwt/dev/jjs/SourceInfoCorrelation.java
new file mode 100644
index 0000000..67bb0e6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceInfoCorrelation.java
@@ -0,0 +1,226 @@
+/*
+ * 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 com.google.gwt.dev.jjs.Correlation.Axis;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tracks file and line information for AST nodes.
+ * 
+ * TODO: make this package-protected?
+ */
+public class SourceInfoCorrelation implements SourceInfo, Serializable {
+
+  /**
+   * Micro-opt for {@link #makeChild(Class, String)}.
+   */
+  private static final SourceInfo[] EMPTY_SOURCEINFO_ARRAY = new SourceInfo[0];
+
+  /**
+   * Any Correlation associated with the SourceInfo.
+   */
+  private final List<Correlation> allCorrelations;
+
+  /**
+   * 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;
+
+  public SourceInfoCorrelation(SourceOrigin origin) {
+    this.origin = origin;
+    allCorrelations = new ArrayList<Correlation>();
+    primaryCorrelations = new EnumMap<Axis, Correlation>(Axis.class);
+  }
+
+  private SourceInfoCorrelation(SourceInfoCorrelation parent, String mutation,
+      String caller, SourceInfo... additionalAncestors) {
+    assert parent != null;
+    assert caller != null;
+    this.origin = parent.origin;
+
+    this.allCorrelations = new ArrayList<Correlation>(parent.allCorrelations);
+    this.primaryCorrelations = new EnumMap<Axis, Correlation>(
+        parent.primaryCorrelations);
+
+    merge(additionalAncestors);
+  }
+
+  /**
+   * Add a Correlation to the SourceInfo.
+   */
+  public void addCorrelation(Correlation c) {
+    if (!isAlreadyInAllCorrelations(c)) {
+      allCorrelations.add(c);
+    }
+    if (!primaryCorrelations.containsKey(c.getAxis())) {
+      primaryCorrelations.put(c.getAxis(), c);
+    }
+  }
+
+  /**
+   * Copy any Correlations from another SourceInfo node if there are no
+   * Correlations on this SourceInfo with the same Axis.
+   */
+  public void copyMissingCorrelationsFrom(SourceInfo other) {
+    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);
+      }
+    }
+  }
+
+  /**
+   * Returns all Correlations applied to this SourceInfo, its parent, additional
+   * ancestor SourceInfo, and any supertype SourceInfos.
+   */
+  public List<Correlation> getAllCorrelations() {
+    return allCorrelations;
+  }
+
+  /**
+   * Returns all Correlations along a given axis applied to this SourceInfo, its
+   * parent, additional ancestor SourceInfo, and any supertype SourceInfos.
+   */
+  public List<Correlation> getAllCorrelations(Axis axis) {
+    List<Correlation> toReturn = new ArrayList<Correlation>();
+    for (Correlation c : getAllCorrelations()) {
+      if (c.getAxis() == axis) {
+        toReturn.add(c);
+      }
+    }
+    return toReturn;
+  }
+
+  public int getEndPos() {
+    return getOrigin().getEndPos();
+  }
+
+  public String getFileName() {
+    return getOrigin().getFileName();
+  }
+
+  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 primaryCorrelations.get(axis);
+  }
+
+  /**
+   * Returns the first Correlations added along each Axis on which a Correlation
+   * has been set.
+   */
+  public Set<Correlation> getPrimaryCorrelations() {
+    return new HashSet<Correlation>(primaryCorrelations.values());
+  }
+
+  public int getStartLine() {
+    return getOrigin().getStartLine();
+  }
+
+  public int getStartPos() {
+    return getOrigin().getStartPos();
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * 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 SourceInfoCorrelation makeChild(Class<?> caller, String description,
+      SourceInfo... merge) {
+    String callerName = caller == null ? "Unrecorded caller" : caller.getName();
+    return new SourceInfoCorrelation(this, description, callerName, merge);
+  }
+
+  /**
+   * 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 merge(SourceInfo... sourceInfos) {
+    for (SourceInfo info : sourceInfos) {
+      if (this == info) {
+        continue;
+      }
+
+      for (Correlation c : info.getAllCorrelations()) {
+        if (!isAlreadyInAllCorrelations(c)) {
+          allCorrelations.add(c);
+        }
+      }
+      if (primaryCorrelations.size() < Axis.values().length
+          && info instanceof SourceInfoCorrelation) {
+        EnumMap<Axis, Correlation> copy = new EnumMap<Axis, Correlation>(
+            ((SourceInfoCorrelation) info).primaryCorrelations);
+        copy.keySet().removeAll(primaryCorrelations.keySet());
+        primaryCorrelations.putAll(copy);
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    return origin.toString();
+  }
+
+  private boolean isAlreadyInAllCorrelations(Correlation c) {
+    // make sure this correlations is not yet in the allCorrelations list
+    boolean alreadyThere = false;
+    Iterator<Correlation> it = allCorrelations.iterator();
+    while ((alreadyThere == false) && (it.hasNext())) {
+      if (it.next().equals(c)) {
+        alreadyThere = true;
+      }
+    }
+    return alreadyThere;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
index 4ef81f4..e2722bd 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
@@ -15,35 +15,84 @@
  */
 package com.google.gwt.dev.jjs;
 
-import org.apache.commons.collections.map.ReferenceMap;
+import com.google.gwt.dev.jjs.Correlation.Axis;
 
-import java.io.Serializable;
 import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
 
 /**
  * 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.
+ * 
+ * TODO: rename this class to make it parallel to {@link SourceInfoCorrelation}?
+ * 
+ * TODO: make this package-protected?
  */
-public final class SourceOrigin implements Serializable {
+public class SourceOrigin implements SourceInfo {
+
+  private static class SourceOriginPos extends SourceOrigin {
+    private final int endPos;
+    private final int startPos;
+
+    private SourceOriginPos(String location, int startLine, int startPos,
+        int endPos) {
+      super(location, startLine);
+      this.startPos = startPos;
+      this.endPos = endPos;
+    }
+
+    @Override
+    public int getEndPos() {
+      return endPos;
+    }
+
+    @Override
+    public int getStartPos() {
+      return startPos;
+    }
+  }
+
+  public static final SourceInfo UNKNOWN = new SourceOrigin("Unknown", 0);
+
   /**
-   * This is synchronized since several threads could operate on it at once
-   * during parallel optimization phases.
+   * Cache to reuse recently-created origins. This is very useful for JS nodes,
+   * since {@link com.google.gwt.dev.js.JsParser} currently only provides line
+   * numbers rather than character positions, so we get a lot of reuse there. We
+   * get barely any reuse in the Java AST. 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));
+  private static final Map<SourceOrigin, SourceOrigin> CANONICAL_SOURCE_ORIGINS = Collections.synchronizedMap(new LinkedHashMap<SourceOrigin, SourceOrigin>(
+      150, 0.75f, true) {
+    @Override
+    protected boolean removeEldestEntry(Entry<SourceOrigin, SourceOrigin> eldest) {
+      return size() > 100;
+    }
+  });
+
+  /**
+   * Creates SourceOrigin nodes.
+   */
+  public static SourceOrigin create(int startPos, int endPos, int startLine,
+      String fileName) {
+    if (startPos < 0 && endPos < 0) {
+      return create(startLine, fileName);
+    }
+
+    return new SourceOriginPos(fileName, startLine, startPos, endPos);
+  }
 
   /**
    * 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) {
+  public static SourceOrigin create(int startLine, String fileName) {
 
-    SourceOrigin newInstance = new SourceOrigin(fileName, startLine, startPos,
-        endPos);
+    SourceOrigin newInstance = new SourceOrigin(fileName, startLine);
     SourceOrigin canonical = CANONICAL_SOURCE_ORIGINS.get(newInstance);
 
     assert canonical == null
@@ -58,21 +107,18 @@
   }
 
   // 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) {
+  private SourceOrigin(String location, int startLine) {
     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;
+  public void addCorrelation(Correlation c) {
+  }
+
+  public void copyMissingCorrelationsFrom(SourceInfo other) {
   }
 
   @Override
@@ -81,29 +127,63 @@
       return false;
     }
     SourceOrigin other = (SourceOrigin) o;
-    return endPos == other.endPos && fileName.equals(other.fileName)
-        && startLine == other.startLine && startPos == other.startPos;
+    return startLine == other.startLine && getEndPos() == other.getEndPos()
+        && getStartPos() == other.getStartPos()
+        && fileName.equals(other.fileName);
+  }
+
+  public List<Correlation> getAllCorrelations() {
+    return Collections.emptyList();
+  }
+
+  public List<Correlation> getAllCorrelations(Axis axis) {
+    return Collections.emptyList();
   }
 
   public int getEndPos() {
-    return endPos;
+    return -1;
   }
 
   public String getFileName() {
     return fileName;
   }
 
+  public SourceOrigin getOrigin() {
+    return this;
+  }
+
+  public Correlation getPrimaryCorrelation(Axis axis) {
+    return null;
+  }
+
+  public Set<Correlation> getPrimaryCorrelations() {
+    return Collections.emptySet();
+  }
+
   public int getStartLine() {
     return startLine;
   }
 
   public int getStartPos() {
-    return startPos;
+    return -1;
   }
 
   @Override
   public int hashCode() {
-    return hash;
+    return 2 + 13 * fileName.hashCode() + 17 * startLine + 29 * getStartPos()
+        + 31 * getEndPos();
+  }
+
+  public SourceInfo makeChild(Class<?> caller, String description) {
+    return this;
+  }
+
+  public SourceInfo makeChild(Class<?> caller, String description,
+      SourceInfo... merge) {
+    return this;
+  }
+
+  public void merge(SourceInfo... sourceInfos) {
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNode.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNode.java
index 7126366..5de8672 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNode.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNode.java
@@ -32,24 +32,13 @@
   private final SourceInfo info;
 
   protected JNode(JProgram program, SourceInfo info) {
+    assert info != null : "SourceInfo must be provided for JNodes";
+    this.info = info;
     if (program == null) {
-      assert (this instanceof JProgram);
       this.program = (JProgram) this;
     } else {
       this.program = program;
     }
-
-    if (info != null) {
-      this.info = info;
-    } else {
-      /*
-       * This indicates a deficiency in the compiler. We use getClass() instead
-       * of the usual class literal to figure out what kind of thing was being
-       * constructed.
-       */
-      this.info = SourceInfo.UNKNOWN.makeChild(getClass(), "Unknown "
-          + getClass().getSimpleName());
-    }
   }
 
   public JProgram getProgram() {
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 e3de02b..b7f4405 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,9 +15,10 @@
  */
 package com.google.gwt.dev.jjs.ast;
 
-import com.google.gwt.dev.jjs.Correlation;
+import com.google.gwt.dev.jjs.CorrelationFactory;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
 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;
@@ -44,7 +45,6 @@
  * Root for the AST representing an entire Java program.
  */
 public class JProgram extends JNode {
-
   private static final class ArrayTypeComparator implements
       Comparator<JArrayType>, Serializable {
     public int compare(JArrayType o1, JArrayType o2) {
@@ -141,15 +141,6 @@
     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) {
@@ -187,14 +178,17 @@
   private final Map<JType, JClassLiteral> classLiterals = new IdentityHashMap<JType, JClassLiteral>();
 
   /**
+   * A factory to create correlations.
+   */
+  private final CorrelationFactory correlator;
+
+  /**
    * Each entry is a HashMap(JType => JArrayType), arranged such that the number
    * of dimensions is that index (plus one) at which the JArrayTypes having that
    * number of dimensions resides.
    */
   private final ArrayList<HashMap<JType, JArrayType>> dimensions = new ArrayList<HashMap<JType, JArrayType>>();
 
-  private final boolean enableSourceInfoDescendants;
-
   private final Map<String, JField> indexedFields = new HashMap<String, JField>();
 
   private final Map<String, JMethod> indexedMethods = new HashMap<String, JMethod>();
@@ -203,28 +197,20 @@
 
   private final Map<JMethod, JMethod> instanceToStaticMap = new IdentityHashMap<JMethod, JMethod>();
 
+  /**
+   * The root intrinsic source info.
+   */
+  private final SourceInfo intrinsic;
+
   private List<JsonObject> jsonTypeTable;
 
-  private final JAbsentArrayDimension literalAbsentArrayDim = new JAbsentArrayDimension(
-      this, createSourceInfoEnabled("Absent array dimension"));
-
-  private final JBooleanLiteral literalFalse = new JBooleanLiteral(this,
-      createSourceInfoEnabled("false literal"), false);
-
-  private final JIntLiteral literalIntNegOne = new JIntLiteral(this,
-      createSourceInfoEnabled("-1 literal"), -1);
-
-  private final JIntLiteral literalIntOne = new JIntLiteral(this,
-      createSourceInfoEnabled("1 literal"), 1);
-
-  private final JIntLiteral literalIntZero = new JIntLiteral(this,
-      createSourceInfoEnabled("0 literal"), 0);
-
-  private final JNullLiteral literalNull = new JNullLiteral(this,
-      createSourceInfoEnabled("null literal"));
-
-  private final JBooleanLiteral literalTrue = new JBooleanLiteral(this,
-      createSourceInfoEnabled("true literal"), true);
+  private final JAbsentArrayDimension literalAbsentArrayDim;
+  private final JBooleanLiteral literalFalse;
+  private final JIntLiteral literalIntNegOne;
+  private final JIntLiteral literalIntOne;
+  private final JIntLiteral literalIntZero;
+  private final JNullLiteral literalNull;
+  private final JBooleanLiteral literalTrue;
 
   private JField nullField;
 
@@ -241,42 +227,33 @@
 
   private final Map<JMethod, JMethod> staticToInstanceMap = new IdentityHashMap<JMethod, JMethod>();
 
-  private final JPrimitiveType typeBoolean = new JPrimitiveType(this,
-      "boolean", "Z", "java.lang.Boolean", literalFalse);
+  private final JPrimitiveType typeBoolean;
 
-  private final JPrimitiveType typeByte = new JPrimitiveType(this, "byte", "B",
-      "java.lang.Byte", literalIntZero);
+  private final JPrimitiveType typeByte;
 
-  private final JPrimitiveType typeChar = new JPrimitiveType(this, "char", "C",
-      "java.lang.Character", getLiteralChar((char) 0));
+  private final JPrimitiveType typeChar;
 
   private JClassType typeClass;
 
-  private final JPrimitiveType typeDouble = new JPrimitiveType(this, "double",
-      "D", "java.lang.Double", getLiteralDouble(0));
+  private final JPrimitiveType typeDouble;
 
-  private final JPrimitiveType typeFloat = new JPrimitiveType(this, "float",
-      "F", "java.lang.Float", getLiteralFloat(0));
+  private final JPrimitiveType typeFloat;
 
   private Map<JClassType, Integer> typeIdMap = new HashMap<JClassType, Integer>();
 
-  private final JPrimitiveType typeInt = new JPrimitiveType(this, "int", "I",
-      "java.lang.Integer", literalIntZero);
+  private final JPrimitiveType typeInt;
 
   private JClassType typeJavaLangEnum;
 
   private JClassType typeJavaLangObject;
 
-  private final JPrimitiveType typeLong = new JPrimitiveType(this, "long", "J",
-      "java.lang.Long", getLiteralLong(0));
+  private final JPrimitiveType typeLong;
 
   private final Map<String, JReferenceType> typeNameMap = new HashMap<String, JReferenceType>();
 
-  private final JNullType typeNull = new JNullType(this,
-      createSourceInfoEnabled("null type"));
+  private final JNullType typeNull;
 
-  private final JPrimitiveType typeShort = new JPrimitiveType(this, "short",
-      "S", "java.lang.Short", literalIntZero);
+  private final JPrimitiveType typeShort;
 
   private JClassType typeSpecialClassLiteralHolder;
 
@@ -284,15 +261,14 @@
 
   private JClassType typeString;
 
-  private final JPrimitiveType typeVoid = new JPrimitiveType(this, "void", "V",
-      "java.lang.Void", null);
+  private final JPrimitiveType typeVoid;
 
   private final SourceInfo stringPoolSourceInfo;
 
   private final Map<String, JStringLiteral> stringLiteralMap = new HashMap<String, JStringLiteral>();
 
   public JProgram() {
-    this(false);
+    this(new CorrelationFactory.DummyCorrelationFactory());
   }
 
   /**
@@ -303,20 +279,64 @@
    *          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"));
+  public JProgram(CorrelationFactory correlator) {
+    super(null, correlator.makeSourceInfo(SourceOrigin.create(0,
+        JProgram.class.getName())));
 
-    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));
+    this.correlator = correlator;
+    intrinsic = createSourceInfo(0, getClass().getName());
+
+    literalAbsentArrayDim = new JAbsentArrayDimension(this,
+        createLiteralSourceInfo("Absent array dimension"));
+
+    literalFalse = new JBooleanLiteral(this, createLiteralSourceInfo(
+        "false literal", Literal.BOOLEAN), false);
+
+    literalIntNegOne = new JIntLiteral(this, createLiteralSourceInfo(
+        "-1 literal", Literal.INT), -1);
+
+    literalIntOne = new JIntLiteral(this, createLiteralSourceInfo("1 literal",
+        Literal.INT), 1);
+
+    literalIntZero = new JIntLiteral(this, createLiteralSourceInfo("0 literal",
+        Literal.INT), 0);
+
+    literalNull = new JNullLiteral(this, createLiteralSourceInfo(
+        "null literal", Literal.NULL));
+
+    literalTrue = new JBooleanLiteral(this, createLiteralSourceInfo(
+        "true literal", Literal.BOOLEAN), true);
+
+    typeBoolean = new JPrimitiveType(this, "boolean", "Z", "java.lang.Boolean",
+        literalFalse);
+
+    typeByte = new JPrimitiveType(this, "byte", "B", "java.lang.Byte",
+        literalIntZero);
+
+    typeChar = new JPrimitiveType(this, "char", "C", "java.lang.Character",
+        getLiteralChar((char) 0));
+
+    typeDouble = new JPrimitiveType(this, "double", "D", "java.lang.Double",
+        getLiteralDouble(0));
+
+    typeFloat = new JPrimitiveType(this, "float", "F", "java.lang.Float",
+        getLiteralFloat(0));
+
+    typeInt = new JPrimitiveType(this, "int", "I", "java.lang.Integer",
+        literalIntZero);
+
+    typeLong = new JPrimitiveType(this, "long", "J", "java.lang.Long",
+        getLiteralLong(0));
+
+    typeNull = new JNullType(this, createLiteralSourceInfo("null type"));
+
+    typeShort = new JPrimitiveType(this, "short", "S", "java.lang.Short",
+        literalIntZero);
+
+    typeVoid = new JPrimitiveType(this, "void", "V", "java.lang.Void", null);
+
+    stringPoolSourceInfo = createLiteralSourceInfo("String pool",
+        Literal.STRING);
   }
 
   public void addEntryMethod(JMethod entryPoint) {
@@ -506,8 +526,8 @@
    */
   public SourceInfo createSourceInfo(int startPos, int endPos, int startLine,
       String fileName) {
-    return new SourceInfoJava(startPos, endPos, startLine, fileName,
-        enableSourceInfoDescendants);
+    return correlator.makeSourceInfo(SourceOrigin.create(startPos, endPos,
+        startLine, fileName));
   }
 
   /**
@@ -515,8 +535,7 @@
    * location.
    */
   public SourceInfo createSourceInfo(int startLine, String fileName) {
-    return new SourceInfoJava(-1, -1, startLine, fileName,
-        enableSourceInfoDescendants);
+    return correlator.makeSourceInfo(SourceOrigin.create(startLine, fileName));
   }
 
   /**
@@ -556,6 +575,10 @@
     return allEntryMethods;
   }
 
+  public CorrelationFactory getCorrelator() {
+    return correlator;
+  }
+
   public List<JReferenceType> getDeclaredTypes() {
     return allTypes;
   }
@@ -624,7 +647,7 @@
   public JCharLiteral getLiteralChar(char c) {
     // could be interned
     SourceInfo info = createSourceInfoSynthetic(JProgram.class, c + " literal");
-    info.addCorrelation(Correlation.by(Literal.CHAR));
+    info.addCorrelation(correlator.by(Literal.CHAR));
     return new JCharLiteral(this, info, c);
   }
 
@@ -665,7 +688,7 @@
 
       SourceInfo literalInfo = createSourceInfoSynthetic(JProgram.class,
           "class literal for " + type.getName());
-      literalInfo.addCorrelation(Correlation.by(Literal.CLASS));
+      literalInfo.addCorrelation(correlator.by(Literal.CLASS));
       classLiteral = new JClassLiteral(this, literalInfo, type, field);
       classLiterals.put(type, classLiteral);
     } else {
@@ -693,14 +716,14 @@
   public JDoubleLiteral getLiteralDouble(double d) {
     // could be interned
     SourceInfo info = createSourceInfoSynthetic(JProgram.class, d + " literal");
-    info.addCorrelation(Correlation.by(Literal.DOUBLE));
+    info.addCorrelation(correlator.by(Literal.DOUBLE));
     return new JDoubleLiteral(this, info, d);
   }
 
   public JFloatLiteral getLiteralFloat(float f) {
     // could be interned
     SourceInfo info = createSourceInfoSynthetic(JProgram.class, f + " literal");
-    info.addCorrelation(Correlation.by(Literal.FLOAT));
+    info.addCorrelation(correlator.by(Literal.FLOAT));
     return new JFloatLiteral(this, info, f);
   }
 
@@ -716,7 +739,7 @@
         // could be interned
         SourceInfo info = createSourceInfoSynthetic(JProgram.class, i
             + " literal");
-        info.addCorrelation(Correlation.by(Literal.INT));
+        info.addCorrelation(correlator.by(Literal.INT));
         return new JIntLiteral(this, info, i);
       }
     }
@@ -724,7 +747,7 @@
 
   public JLongLiteral getLiteralLong(long l) {
     SourceInfo info = createSourceInfoSynthetic(JProgram.class, l + " literal");
-    info.addCorrelation(Correlation.by(Literal.LONG));
+    info.addCorrelation(correlator.by(Literal.LONG));
     return new JLongLiteral(this, info, l);
   }
 
@@ -1176,4 +1199,13 @@
     return count;
   }
 
+  private SourceInfo createLiteralSourceInfo(String description) {
+    return intrinsic.makeChild(getClass(), description);
+  }
+
+  private SourceInfo createLiteralSourceInfo(String description, Literal literal) {
+    SourceInfo child = createLiteralSourceInfo(description);
+    child.addCorrelation(correlator.by(literal));
+    return child;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/SourceInfoJava.java b/dev/core/src/com/google/gwt/dev/jjs/ast/SourceInfoJava.java
deleted file mode 100644
index 07e4267..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/SourceInfoJava.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.jjs.ast;
-
-import com.google.gwt.dev.jjs.SourceInfo;
-
-/**
- * An implementation of SourceInfo representing SourceInfo nodes derived from
- * the Java AST. Instances of this class should only be constructed by JProgram.
- */
-public class SourceInfoJava extends SourceInfo {
-  /**
-   * Indicates that an AST element is an intrinsic element of the AST and has no
-   * meaningful source location. This is typically used by singleton AST
-   * elements or for literal values.
-   */
-  public static final SourceInfo INTRINSIC = new Immutable(0, 0, 0,
-      "Java intrinsics", true);
-
-  /**
-   * Called only from JProgram.
-   */
-  SourceInfoJava(int startPos, int endPos, int startLine, String fileName,
-      boolean createDescendants) {
-    super(startPos, endPos, startLine, fileName, createDescendants);
-  }
-}
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 16daf63..ae14c5b 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
@@ -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;
@@ -189,7 +188,7 @@
         mapParameters(newMethod, ctorDecl);
         // original params are now frozen
 
-        info.addCorrelation(Correlation.by(newMethod));
+        info.addCorrelation(program.getCorrelator().by(newMethod));
 
         int syntheticParamCount = 0;
         ReferenceBinding declaringClass = b.declaringClass;
@@ -287,7 +286,7 @@
         SourceInfo info = makeSourceInfo(methodDeclaration, enclosingType);
         JMethod newMethod = processMethodBinding(b, enclosingType, info);
         mapParameters(newMethod, methodDeclaration);
-        info.addCorrelation(Correlation.by(newMethod));
+        info.addCorrelation(program.getCorrelator().by(newMethod));
 
         if (newMethod.isNative()) {
           processNativeMethod(methodDeclaration, info, enclosingType, newMethod);
@@ -320,7 +319,7 @@
       JType type = (JType) typeMap.get(binding.type);
       JField field = program.createEnumField(info, binding.name,
           (JEnumType) enclosingType, (JClassType) type, binding.original().id);
-      info.addCorrelation(Correlation.by(field));
+      info.addCorrelation(program.getCorrelator().by(field));
       typeMap.put(binding, field);
       return field;
     }
@@ -349,7 +348,7 @@
       JField field = program.createField(info, binding.name, enclosingType,
           type, binding.isStatic(), disposition);
       typeMap.put(binding, field);
-      info.addCorrelation(Correlation.by(field));
+      info.addCorrelation(program.getCorrelator().by(field));
       return field;
     }
 
@@ -360,7 +359,7 @@
           BuildDeclMapVisitor.class, "Field " + String.valueOf(binding.name));
       JField field = program.createField(info, binding.name, enclosingType,
           type, false, Disposition.FINAL);
-      info.addCorrelation(Correlation.by(field));
+      info.addCorrelation(program.getCorrelator().by(field));
       if (binding.matchingField != null) {
         typeMap.put(binding.matchingField, field);
       }
@@ -882,7 +881,7 @@
           assert (false);
           return false;
         }
-        info.addCorrelation(Correlation.by(newType));
+        info.addCorrelation(program.getCorrelator().by(newType));
 
         /**
          * We emulate static initializers and instance initializers as methods.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
index 5b18dcb..3e7f668 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
@@ -440,7 +440,7 @@
     }
 
     if (temp == null) {
-      temp = program.createLocal(null,
+      temp = program.createLocal(currentMethodBody.getSourceInfo(),
           (getTempPrefix() + localCounter++).toCharArray(), type, false,
           currentMethodBody);
     }
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 60cc6fa..a19f754 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
@@ -17,7 +17,6 @@
 
 import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
-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.JsOutputOption;
@@ -298,7 +297,8 @@
             polyName = interfaceScope.declareName(mangleName, name);
           }
           // Record this as an alias, not the primary name
-          x.getSourceInfo().addCorrelation(Correlation.by(polyName, true));
+          x.getSourceInfo().addCorrelation(
+              program.getCorrelator().by(polyName, true));
           polymorphicNames.put(x, polyName);
         }
       }
@@ -314,7 +314,7 @@
       assert x.getEnclosingType() != null;
       String mangleName = mangleNameForGlobal(x);
       globalName = topScope.declareName(mangleName, name);
-      x.getSourceInfo().addCorrelation(Correlation.by(globalName));
+      x.getSourceInfo().addCorrelation(program.getCorrelator().by(globalName));
       names.put(x, globalName);
       recordSymbol(x, globalName);
 
@@ -331,7 +331,8 @@
         jsFunction = new JsFunction(sourceInfo, topScope, globalName, true);
       }
       methodBodyMap.put(x.getBody(), jsFunction);
-      jsFunction.getSourceInfo().addCorrelation(Correlation.by(globalName));
+      jsFunction.getSourceInfo().addCorrelation(
+          program.getCorrelator().by(globalName));
       push(jsFunction.getScope());
       return true;
     }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java
index 1ca37e0..4432845 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java
@@ -26,7 +26,6 @@
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
 import com.google.gwt.dev.js.ast.JsVisitable;
 import com.google.gwt.dev.js.ast.JsVisitor;
-import com.google.gwt.dev.js.ast.SourceInfoJs;
 
 /**
  * Searches for method invocations in constructor expressions that would not
@@ -34,11 +33,8 @@
  */
 public class JsConstructExpressionVisitor extends JsVisitor {
 
-  private static final int PRECEDENCE_NEW = JsPrecedenceVisitor.exec(new JsNew(
-      SourceInfoJs.INTRINSIC));
-
   public static boolean exec(JsExpression expression) {
-    if (JsPrecedenceVisitor.exec(expression) < PRECEDENCE_NEW) {
+    if (JsPrecedenceVisitor.exec(expression) < JsPrecedenceVisitor.PRECEDENCE_NEW) {
       return true;
     }
     JsConstructExpressionVisitor visitor = new JsConstructExpressionVisitor();
@@ -120,7 +116,7 @@
       int precedence = JsPrecedenceVisitor.exec(expression);
       // Only visit expressions that won't automatically be surrounded by
       // parentheses
-      if (precedence < PRECEDENCE_NEW) {
+      if (precedence < JsPrecedenceVisitor.PRECEDENCE_NEW) {
         return node;
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java
index a0524b8..fa31c9b 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java
@@ -85,6 +85,8 @@
  */
 class JsPrecedenceVisitor extends JsVisitor {
 
+  static final int PRECEDENCE_NEW = 15;
+
   public static int exec(JsExpression expression) {
     JsPrecedenceVisitor visitor = new JsPrecedenceVisitor();
     visitor.accept(expression);
@@ -223,7 +225,7 @@
 
   @Override
   public boolean visit(JsNew x, JsContext<JsExpression> ctx) {
-    answer = 15;
+    answer = PRECEDENCE_NEW;
     return false;
   }
 
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 33dd57f..dc073f9 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,8 +15,9 @@
  */
 package com.google.gwt.dev.js.ast;
 
-import com.google.gwt.dev.jjs.Correlation;
+import com.google.gwt.dev.jjs.CorrelationFactory;
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.jjs.Correlation.Axis;
 import com.google.gwt.dev.jjs.Correlation.Literal;
 
@@ -27,30 +28,23 @@
  * 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(
-      createSourceInfoEnabled("debugger statement"));
+  private final CorrelationFactory correlator;
 
-  private final JsEmpty emptyStmt = new JsEmpty(
-      createSourceInfoEnabled("Empty statement"));
+  private final JsStatement debuggerStmt;
 
-  private final boolean enableSourceInfoDescendants;
+  private final JsEmpty emptyStmt;
 
-  private final JsBooleanLiteral falseLiteral = new JsBooleanLiteral(
-      createSourceInfoEnabled("false literal"), false);
+  private final JsBooleanLiteral falseLiteral;
 
   private JsProgramFragment[] fragments;
 
-  private final JsNullLiteral nullLiteral = new JsNullLiteral(
-      createSourceInfoEnabled("null literal"));
+  /**
+   * The root intrinsic source info.
+   */
+  private final SourceInfo intrinsic;
+
+  private final JsNullLiteral nullLiteral;
 
   private final Map<Double, JsNumberLiteral> numberLiteralMap = new HashMap<Double, JsNumberLiteral>();
 
@@ -64,11 +58,10 @@
 
   private final JsScope topScope;
 
-  private final JsBooleanLiteral trueLiteral = new JsBooleanLiteral(
-      createSourceInfoEnabled("true literal"), true);
+  private final JsBooleanLiteral trueLiteral;
 
   public JsProgram() {
-    this(false);
+    this(new CorrelationFactory.DummyCorrelationFactory());
   }
 
   /**
@@ -79,28 +72,35 @@
    *          feature will collect extra data during the compilation cycle, but
    *          at a cost of memory and object allocations.
    */
-  public JsProgram(boolean soycEnabled) {
-    super(SourceInfoJs.INTRINSIC.makeChild(JsProgram.class,
-        "JavaScript program"));
-    this.enableSourceInfoDescendants = soycEnabled;
+  public JsProgram(CorrelationFactory correlator) {
+    super(correlator.makeSourceInfo(SourceOrigin.create(0,
+        JsProgram.class.getName())));
+
+    this.correlator = correlator;
+    intrinsic = createSourceInfo(0, getClass().getName());
 
     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));
+
+    debuggerStmt = new JsDebugger(createLiteralSourceInfo("debugger statement"));
+    emptyStmt = new JsEmpty(createLiteralSourceInfo("Empty statement"));
+    falseLiteral = new JsBooleanLiteral(createLiteralSourceInfo(
+        "false literal", Literal.JS_BOOLEAN), false);
+    nullLiteral = new JsNullLiteral(createLiteralSourceInfo("null literal",
+        Literal.JS_NULL));
+    trueLiteral = new JsBooleanLiteral(createLiteralSourceInfo("true literal",
+        Literal.JS_BOOLEAN), true);
+
     trueLiteral.getSourceInfo().addCorrelation(
-        Correlation.by(Literal.JS_BOOLEAN));
-    stringPoolSourceInfo = createSourceInfoSynthetic(JsProgram.class,
-        "String pool");
-    stringPoolSourceInfo.addCorrelation(Correlation.by(Literal.JS_STRING));
+        correlator.by(Literal.JS_BOOLEAN));
+    stringPoolSourceInfo = createLiteralSourceInfo("String pool",
+        Literal.JS_STRING);
   }
 
   public SourceInfo createSourceInfo(int lineNumber, String location) {
-    return new SourceInfoJs(-1, -1, lineNumber, location,
-        enableSourceInfoDescendants);
+    return correlator.makeSourceInfo(SourceOrigin.create(lineNumber, location));
   }
 
   public SourceInfo createSourceInfoSynthetic(Class<?> caller,
@@ -170,7 +170,7 @@
       if (lit == null) {
         info = createSourceInfoSynthetic(JsProgram.class, "Number literal "
             + value);
-        info.addCorrelation(Correlation.by(Literal.JS_NUMBER));
+        info.addCorrelation(correlator.by(Literal.JS_NUMBER));
         lit = new JsNumberLiteral(info, value);
         numberLiteralMap.put(value, lit);
       }
@@ -181,7 +181,7 @@
       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));
+        info.addCorrelation(correlator.by(Literal.JS_NUMBER));
       }
       return new JsNumberLiteral(info, value);
     }
@@ -231,7 +231,7 @@
   public JsNameRef getUndefinedLiteral() {
     SourceInfo info = createSourceInfoSynthetic(JsProgram.class,
         "undefined reference");
-    info.addCorrelation(Correlation.by(Literal.JS_UNDEFINED));
+    info.addCorrelation(correlator.by(Literal.JS_UNDEFINED));
     return rootScope.findExistingName("undefined").makeRef(info);
   }
 
@@ -251,4 +251,14 @@
     }
     v.endVisit(this, ctx);
   }
+
+  private SourceInfo createLiteralSourceInfo(String description) {
+    return intrinsic.makeChild(getClass(), description);
+  }
+
+  private SourceInfo createLiteralSourceInfo(String description, Literal literal) {
+    SourceInfo child = createLiteralSourceInfo(description);
+    child.addCorrelation(correlator.by(literal));
+    return child;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/SourceInfoJs.java b/dev/core/src/com/google/gwt/dev/js/ast/SourceInfoJs.java
deleted file mode 100644
index 9db902d..0000000
--- a/dev/core/src/com/google/gwt/dev/js/ast/SourceInfoJs.java
+++ /dev/null
@@ -1,41 +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.ast;
-
-import com.google.gwt.dev.jjs.SourceInfo;
-
-/**
- * An implementation of SourceInfo representing SourceInfo nodes derived from
- * the JavaScript AST. Instances of this class should only be constructed by
- * JsProgram.
- */
-public class SourceInfoJs extends SourceInfo {
-  /**
-   * Indicates that an AST element is an intrinsic element of the AST and has no
-   * meaningful source location. This is typically used by singleton AST
-   * elements or for literal values.
-   */
-  public static final SourceInfo INTRINSIC = new Immutable(0, 0, 0,
-      "Js intrinsics", true);
-
-  /**
-   * Called only from JsProgram.
-   */
-  SourceInfoJs(int startPos, int endPos, int startLine, String fileName,
-      boolean createDescendants) {
-    super(startPos, endPos, startLine, fileName, createDescendants);
-  }
-}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
index 35431e5..4fc4188 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.dev.js;
 
-import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsVisitor;
@@ -134,7 +134,7 @@
   }
 
   private void doTest(String js) throws Exception {
-    List<JsStatement> expected = JsParser.parse(SourceInfo.UNKNOWN,
+    List<JsStatement> expected = JsParser.parse(SourceOrigin.UNKNOWN,
         new JsProgram().getScope(), new StringReader(js));
     List<JsStatement> actual = parse(expected, true);
     ComparingVisitor.exec(expected, actual);
@@ -148,7 +148,7 @@
     TextOutput text = new DefaultTextOutput(compact);
     JsVisitor generator = new JsSourceGenerationVisitor(text);
     generator.acceptList(expected);
-    return JsParser.parse(SourceInfo.UNKNOWN, new JsProgram().getScope(),
+    return JsParser.parse(SourceOrigin.UNKNOWN, new JsProgram().getScope(),
         new StringReader(text.toString()));
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
index 5e01daf..9242f6e 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.dev.js;
 
-import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsVisitor;
@@ -75,7 +75,7 @@
   }
 
   private String parse(String js) throws Exception {
-    List<JsStatement> statements = JsParser.parse(SourceInfo.UNKNOWN,
+    List<JsStatement> statements = JsParser.parse(SourceOrigin.UNKNOWN,
         new JsProgram().getScope(), new StringReader(js));
     TextOutput text = new DefaultTextOutput(true);
     JsVisitor generator = new JsToStringGenerationVisitor(text);
diff --git a/dev/oophm/overlay/com/google/gwt/dev/util/Jsni.java b/dev/oophm/overlay/com/google/gwt/dev/util/Jsni.java
index 2df7d42..18a40a5 100644
--- a/dev/oophm/overlay/com/google/gwt/dev/util/Jsni.java
+++ b/dev/oophm/overlay/com/google/gwt/dev/util/Jsni.java
@@ -138,9 +138,10 @@
 
         // Use a clone instead of modifying the original JSNI
         // __gwt_makeTearOff(obj, dispId, paramCount)
-        JsInvocation rewritten = new JsInvocation(SourceInfo.UNKNOWN);
-        rewritten.setQualifier(new JsNameRef(SourceInfo.UNKNOWN,
-            "__gwt_makeTearOff"));
+        SourceInfo newSourceInfo = x.getSourceInfo().makeChild(getClass(),
+            "Replace JSNI ref for hosted mode");
+        JsInvocation rewritten = new JsInvocation(newSourceInfo);
+        rewritten.setQualifier(new JsNameRef(newSourceInfo, "__gwt_makeTearOff"));
 
         List<JsExpression> arguments = rewritten.getArguments();
         if (q == null) {
@@ -198,12 +199,14 @@
               paramCount = ((Constructor<?>) member).getParameterTypes().length;
             }
 
-            JsInvocation inner = new JsInvocation(SourceInfo.UNKNOWN);
-            inner.setQualifier(new JsNameRef(SourceInfo.UNKNOWN,
+            SourceInfo newSourceInfo = x.getSourceInfo().makeChild(getClass(),
+                "Replace JSNI ref for hosted mode");
+            JsInvocation inner = new JsInvocation(newSourceInfo);
+            inner.setQualifier(new JsNameRef(newSourceInfo,
                 "__gwt_makeJavaInvoke"));
             inner.getArguments().add(program.getNumberLiteral(paramCount));
 
-            JsInvocation outer = new JsInvocation(SourceInfo.UNKNOWN);
+            JsInvocation outer = new JsInvocation(newSourceInfo);
             outer.setQualifier(inner);
             JsExpression q = ref.getQualifier();
             if (q == null) {